<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. //! Windows file path handling use ascii::AsciiCast; use c_str::{CString, ToCStr}; use cast; use cmp::Eq; use from_str::FromStr; use iter::{AdditiveIterator, Extendable, Iterator}; use option::{Option, Some, None}; use str; use str::{OwnedStr, Str, StrVector}; use util; use vec::Vector; use super::{GenericPath, GenericPathUnsafe}; /// Iterator that yields successive components of a Path pub type ComponentIter<'self> = str::CharSplitIterator<'self, char>; /// Represents a Windows path // Notes for Windows path impl: // The MAX_PATH is 260, but 253 is the practical limit due to some API bugs // See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx for good information // about windows paths. // That same page puts a bunch of restrictions on allowed characters in a path. // `foo.txt` means "relative to current drive", but will not be considered to be absolute here // as `∃P | P.join("foo.txt") != "foo.txt"`. // `C:` is interesting, that means "the current directory on drive C". // Long absolute paths need to have ? prefix (or, for UNC, ?UNC). I think that can be // ignored for now, though, and only added in a hypothetical .to_pwstr() function. // However, if a path is parsed that has ?, this needs to be preserved as it disables the // processing of "." and ".." components and / as a separator. // Experimentally, ?foo is not the same thing as foo. // Also, foo is not valid either (certainly not equivalent to foo). // Similarly, C:Users is not equivalent to C:Users, although C:Usersfoo is equivalent // to C:Usersfoo. In fact the command prompt treats C:foobar as UNC path. But it might be // best to just ignore that and normalize it to C:foobar. // // Based on all this, I think the right approach is to do the following: // * Require valid utf-8 paths. Windows API may use WCHARs, but we don't, and utf-8 is convertible // to UTF-16 anyway (though does Windows use UTF-16 or UCS-2? Not sure). // * Parse the prefixes ?UNC, ?, and . explicitly. // * If ?UNC, treat following two path components as servershare. Don't error for missing //   servershare. // * If ?, parse disk from following component, if present. Don't error for missing disk. // * If ., treat rest of path as just regular components. I don't know how . and .. are handled //   here, they probably aren't, but I'm not going to worry about that. // * Else if starts with , treat following two components as servershare. Don't error for missing //   servershare. // * Otherwise, attempt to parse drive from start of path. // // The only error condition imposed here is valid utf-8. All other invalid paths are simply // preserved by the data structure; let the Windows API error out on them. #[deriving(Clone, DeepClone)] pub struct Path { priv repr: ~str, // assumed to never be empty priv prefix: Option<PathPrefix>, priv sepidx: Option<uint> // index of the final separator in the non-prefix portion of repr } 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> { if contains_nul(s.as_bytes()) { None } else { Some(unsafe { GenericPathUnsafe::from_str_unchecked(s) }) } } } impl ToCStr for Path { #[inline] fn to_c_str(&self) -> CString { // The Path impl guarantees no embedded NULs 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 { /// See `GenericPathUnsafe::from_vec_unchecked`. /// /// # Failure /// /// Raises the `str::not_utf8` condition if not valid UTF-8. #[inline] unsafe fn from_vec_unchecked(path: &[u8]) -> Path { if !str::is_utf8(path) { let path = str::from_utf8(path); // triggers not_utf8 condition GenericPathUnsafe::from_str_unchecked(path) } else { GenericPathUnsafe::from_str_unchecked(cast::transmute(path)) } } #[inline] unsafe fn from_str_unchecked(path: &str) -> Path { let (prefix, path) = Path::normalize_(path); assert!(!path.is_empty()); let mut ret = Path{ repr: path, prefix: prefix, sepidx: None }; ret.update_sepidx(); ret } /// See `GenericPathUnsafe::set_dirname_unchecked`. /// /// # Failure /// /// Raises the `str::not_utf8` condition if not valid UTF-8. #[inline] unsafe fn set_dirname_unchecked(&mut self, dirname: &[u8]) { if !str::is_utf8(dirname) { let dirname = str::from_utf8(dirname); // triggers not_utf8 condition self.set_dirname_str_unchecked(dirname); } else { self.set_dirname_str_unchecked(cast::transmute(dirname)) } } unsafe fn set_dirname_str_unchecked(&mut self, dirname: &str) { match self.sepidx_or_prefix_len() { None if "." == self.repr || ".." == self.repr => { self.update_normalized(dirname); } None => { let mut s = str::with_capacity(dirname.len() + self.repr.len() + 1); s.push_str(dirname); s.push_char(sep); s.push_str(self.repr); self.update_normalized(s); } Some((_,idxa,end)) if self.repr.slice(idxa,end) == ".." => { self.update_normalized(dirname); } Some((_,idxa,end)) if dirname.is_empty() => { let (prefix, path) = Path::normalize_(self.repr.slice(idxa,end)); self.repr = path; self.prefix = prefix; self.update_sepidx(); } Some((idxb,idxa,end)) => { let idx = if dirname.ends_with("") { idxa } else { let prefix = parse_prefix(dirname); if prefix == Some(DiskPrefix) && prefix_len(prefix) == dirname.len() { idxa } else { idxb } }; let mut s = str::with_capacity(dirname.len() + end - idx); s.push_str(dirname); s.push_str(self.repr.slice(idx,end)); self.update_normalized(s); } } } /// See `GenericPathUnsafe::set_filename_unchecekd`. /// /// # Failure /// /// Raises the `str::not_utf8` condition if not valid UTF-8. #[inline] unsafe fn set_filename_unchecked(&mut self, filename: &[u8]) { if !str::is_utf8(filename) { let filename = str::from_utf8(filename); // triggers not_utf8 condition self.set_filename_str_unchecked(filename) } else { self.set_filename_str_unchecked(cast::transmute(filename)) } } unsafe fn set_filename_str_unchecked(&mut self, filename: &str) { match self.sepidx_or_prefix_len() { None if ".." == self.repr => { let mut s = str::with_capacity(3 + filename.len()); s.push_str(".."); s.push_char(sep); s.push_str(filename); self.update_normalized(s); } None => { self.update_normalized(filename); } Some((_,idxa,end)) if self.repr.slice(idxa,end) == ".." => { let mut s = str::with_capacity(end + 1 + filename.len()); s.push_str(self.repr.slice_to(end)); s.push_char(sep); s.push_str(filename); self.update_normalized(s); } Some((idxb,idxa,_)) if self.prefix == Some(DiskPrefix) && idxa == self.prefix_len() => { let mut s = str::with_capacity(idxb + filename.len()); s.push_str(self.repr.slice_to(idxb)); s.push_str(filename); self.update_normalized(s); } Some((idxb,_,_)) => { let mut s = str::with_capacity(idxb + 1 + filename.len()); s.push_str(self.repr.slice_to(idxb)); s.push_char(sep); s.push_str(filename); self.update_normalized(s); } } } /// See `GenericPathUnsafe::push_unchecked`. /// /// # Failure /// /// Raises the `str::not_utf8` condition if not valid UTF-8. unsafe fn push_unchecked(&mut self, path: &[u8]) { if !str::is_utf8(path) { let path = str::from_utf8(path); // triggers not_utf8 condition self.push_str_unchecked(path); } else { self.push_str_unchecked(cast::transmute(path)); } } /// See `GenericPathUnsafe::push_str_unchecked`. /// /// Concatenating two Windows Paths is rather complicated. /// For the most part, it will behave as expected, except in the case of /// pushing a volume-relative path, e.g. `C:foo.txt`. Because we have no /// concept of per-volume cwds like Windows does, we can't behave exactly /// like Windows will. Instead, if the receiver is an absolute path on /// the same volume as the new path, it will be treated as the cwd that /// the new path is relative to. Otherwise, the new path will be treated /// as if it were absolute and will replace the receiver outright. unsafe fn push_str_unchecked(&mut self, path: &str) { fn is_vol_abs(path: &str, prefix: Option<PathPrefix>) -> bool { // assume prefix is Some(DiskPrefix) let rest = path.slice_from(prefix_len(prefix)); !rest.is_empty() && rest[0].is_ascii() && is_sep2(rest[0] as char) } fn shares_volume(me: &Path, path: &str) -> bool { // path is assumed to have a prefix of Some(DiskPrefix) match me.prefix { Some(DiskPrefix) => me.repr[0] == path[0].to_ascii().to_upper().to_byte(), Some(VerbatimDiskPrefix) => me.repr[4] == path[0].to_ascii().to_upper().to_byte(), _ => false } } fn is_sep_(prefix: Option<PathPrefix>, u: u8) -> bool { u.is_ascii() && if prefix_is_verbatim(prefix) { is_sep(u as char) } else { is_sep2(u as char) } } fn replace_path(me: &mut Path, path: &str, prefix: Option<PathPrefix>) { let newpath = Path::normalize__(path, prefix); me.repr = match newpath { Some(p) => p, None => path.to_owned() }; me.prefix = prefix; me.update_sepidx(); } fn append_path(me: &mut Path, path: &str) { // appends a path that has no prefix // if me is verbatim, we need to pre-normalize the new path let path_ = if me.is_verbatim() { Path::normalize__(path, None) } else { None }; let pathlen = path_.map_default(path.len(), |p| p.len()); let mut s = str::with_capacity(me.repr.len() + 1 + pathlen); s.push_str(me.repr); let plen = me.prefix_len(); if !(me.repr.len() > plen && me.repr[me.repr.len()-1] == sep as u8) { s.push_char(sep); } match path_ { None => s.push_str(path), Some(p) => s.push_str(p) }; me.update_normalized(s) } if !path.is_empty() { let prefix = parse_prefix(path); match prefix { Some(DiskPrefix) if !is_vol_abs(path, prefix) && shares_volume(self, path) => { // cwd-relative path, self is on the same volume append_path(self, path.slice_from(prefix_len(prefix))); } Some(_) => { // absolute path, or cwd-relative and self is not same volume replace_path(self, path, prefix); } None if !path.is_empty() && is_sep_(self.prefix, path[0]) => { // volume-relative path if self.prefix().is_some() { // truncate self down to the prefix, then append let n = self.prefix_len(); self.repr.truncate(n); append_path(self, path); } else { // we have no prefix, so nothing to be relative to replace_path(self, path, prefix); } } None => { // relative path append_path(self, path); } } } } } impl GenericPath for Path { /// See `GenericPath::as_str` for info. /// Always returns a `Some` value. #[inline] fn as_str<'a>(&'a self) -> Option<&'a str> { Some(self.repr.as_slice()) } #[inline] fn as_vec<'a>(&'a self) -> &'a [u8] { self.repr.as_bytes() } #[inline] fn dirname<'a>(&'a self) -> &'a [u8] { self.dirname_str().unwrap().as_bytes() } /// See `GenericPath::dirname_str` for info. /// Always returns a `Some` value. fn dirname_str<'a>(&'a self) -> Option<&'a str> { Some(match self.sepidx_or_prefix_len() { None if ".." == self.repr => self.repr.as_slice(), None => ".", Some((_,idxa,end)) if self.repr.slice(idxa, end) == ".." => { self.repr.as_slice() } Some((idxb,_,end)) if self.repr.slice(idxb, end) == "" => { self.repr.as_slice() } Some((0,idxa,_)) => self.repr.slice_to(idxa), Some((idxb,idxa,_)) => { match self.prefix { Some(DiskPrefix) | Some(VerbatimDiskPrefix) if idxb == self.prefix_len() => { self.repr.slice_to(idxa) } _ => self.repr.slice_to(idxb) } } }) } #[inline] fn filename<'a>(&'a self) -> &'a [u8] { self.filename_str().unwrap().as_bytes() } /// See `GenericPath::filename_str` for info. /// Always returns a `Some` value. fn filename_str<'a>(&'a self) -> Option<&'a str> { Some(match self.sepidx_or_prefix_len() { None if "." == self.repr || ".." == self.repr => "", None => self.repr.as_slice(), Some((_,idxa,end)) if self.repr.slice(idxa, end) == ".." => "", Some((_,idxa,end)) => self.repr.slice(idxa, end) }) } /// See `GenericPath::filestem_str` for info. /// Always returns a `Some` value. #[inline] fn filestem_str<'a>(&'a self) -> Option<&'a str> { // filestem() returns a byte vector that's guaranteed valid UTF-8 Some(unsafe { cast::transmute(self.filestem()) }) } #[inline] fn extension_str<'a>(&'a self) -> Option<&'a str> { // extension() returns a byte vector that's guaranteed valid UTF-8 self.extension().map_move(|v| unsafe { cast::transmute(v) }) } fn dir_path(&self) -> Path { unsafe { GenericPathUnsafe::from_str_unchecked(self.dirname_str().unwrap()) } } fn file_path(&self) -> Option<Path> { match self.filename_str() { None | Some("") => None, Some(s) => Some(unsafe { GenericPathUnsafe::from_str_unchecked(s) }) } } #[inline] fn push_path(&mut self, path: &Path) { self.push_str(path.as_str().unwrap()) } #[inline] fn pop_opt(&mut self) -> Option<~[u8]> { self.pop_opt_str().map_move(|s| s.into_bytes()) } fn pop_opt_str(&mut self) -> Option<~str> { match self.sepidx_or_prefix_len() { None if "." == self.repr => None, None => { let mut s = ~"."; util::swap(&mut s, &mut self.repr); self.sepidx = None; Some(s) } Some((idxb,idxa,end)) if idxb == idxa && idxb == end => None, Some((idxb,_,end)) if self.repr.slice(idxb, end) == "" => None, Some((idxb,idxa,end)) => { let s = self.repr.slice(idxa, end).to_owned(); let trunc = match self.prefix { Some(DiskPrefix) | Some(VerbatimDiskPrefix) | None => { let plen = self.prefix_len(); if idxb == plen { idxa } else { idxb } } _ => idxb }; self.repr.truncate(trunc); self.update_sepidx(); Some(s) } } } /// See `GenericPath::is_absolute` for info. /// /// A Windows Path is considered absolute only if it has a non-volume prefix, /// or if it has a volume prefix and the path starts with ''. /// A path of `foo` is not considered absolute because it's actually /// relative to the "current volume". A separate method `Path::is_vol_relative` /// is provided to indicate this case. Similarly a path of `C:foo` is not /// considered absolute because it's relative to the cwd on volume C:. A /// separate method `Path::is_cwd_relative` is provided to indicate this case. #[inline] fn is_absolute(&self) -> bool { match self.prefix { Some(DiskPrefix) => { let rest = self.repr.slice_from(self.prefix_len()); rest.len() > 0 && rest[0] == sep as u8 } Some(_) => true, None => false } } fn is_ancestor_of(&self, other: &Path) -> bool { if !self.equiv_prefix(other) { false } else if self.is_absolute() != other.is_absolute() || self.is_vol_relative() != other.is_vol_relative() { false } else { let mut ita = self.component_iter(); let mut itb = other.component_iter(); if "." == self.repr { return itb.next() != Some(".."); } loop { match (ita.next(), itb.next()) { (None, _) => break, (Some(a), Some(b)) if a == b => { loop }, (Some(a), _) if a == ".." => { // if ita contains only .. components, it's an ancestor return ita.all(|x| x == ".."); } _ => return false } } true } } fn path_relative_from(&self, base: &Path) -> Option<Path> { fn comp_requires_verbatim(s: &str) -> bool { s == "." || s == ".." || s.contains_char(sep2) } if !self.equiv_prefix(base) { // prefixes differ if self.is_absolute() { Some(self.clone()) } else if self.prefix == Some(DiskPrefix) && base.prefix == Some(DiskPrefix) { // both drives, drive letters must differ or they'd be equiv Some(self.clone()) } else { None } } else if self.is_absolute() != base.is_absolute() { if self.is_absolute() { Some(self.clone()) } else { None } } else if self.is_vol_relative() != base.is_vol_relative() { if self.is_vol_relative() { Some(self.clone()) } else { None } } else { let mut ita = self.component_iter(); let mut itb = base.component_iter(); let mut comps = ~[]; let a_verb = self.is_verbatim(); let b_verb = base.is_verbatim(); loop { match (ita.next(), itb.next()) { (None, None) => break, (Some(a), None) if a_verb && comp_requires_verbatim(a) => { return Some(self.clone()) } (Some(a), None) => { comps.push(a); if !a_verb { comps.extend(&mut ita); break; } } (None, _) => comps.push(".."), (Some(a), Some(b)) if comps.is_empty() && a == b => (), (Some(a), Some(b)) if !b_verb && b == "." => { if a_verb && comp_requires_verbatim(a) { return Some(self.clone()) } else { comps.push(a) } } (Some(_), Some(b)) if !b_verb && b == ".." => return None, (Some(a), Some(_)) if a_verb && comp_requires_verbatim(a) => { return Some(self.clone()) } (Some(a), Some(_)) => { comps.push(".."); for _ in itb { comps.push(".."); } comps.push(a); if !a_verb { comps.extend(&mut ita); break; } } } } Some(Path::from_str(comps.connect(""))) } } } impl Path { /// Returns a new Path from a byte vector /// /// # Failure /// /// Raises the `null_byte` condition if the vector contains a NUL. /// Raises the `str::not_utf8` condition if invalid UTF-8. #[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 vector 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.into_bytes() } /// Converts the Path into an owned string /// Returns an Option for compatibility with posix::Path, but the /// return value will always be Some. pub fn into_str(self) -> Option<~str> { Some(self.repr) } /// Returns a normalized string representation of a path, by removing all empty /// components, and unnecessary . and .. components. pub fn normalize<S: Str>(s: S) -> ~str { let (_, path) = Path::normalize_(s); path } /// Returns an iterator that yields each component of the path in turn. /// Does not yield the path prefix (including server/share components in UNC paths). /// Does not distinguish between volume-relative and relative paths, e.g. /// abc and abc. /// Does not distinguish between absolute and cwd-relative paths, e.g. /// C:foo and C:foo. pub fn component_iter<'a>(&'a self) -> ComponentIter<'a> { let s = match self.prefix { Some(_) => { let plen = self.prefix_len(); if self.repr.len() > plen && self.repr[plen] == sep as u8 { self.repr.slice_from(plen+1) } else { self.repr.slice_from(plen) } } None if self.repr[0] == sep as u8 => self.repr.slice_from(1), None => self.repr.as_slice() }; let ret = s.split_terminator_iter(sep); ret } /// Returns whether the path is considered "volume-relative", which means a path /// that looks like "foo". Paths of this form are relative to the current volume, /// but absolute within that volume. #[inline] pub fn is_vol_relative(&self) -> bool { self.prefix.is_none() && self.repr[0] == sep as u8 } /// Returns whether the path is considered "cwd-relative", which means a path /// with a volume prefix that is not absolute. This look like "C:foo.txt". Paths /// of this form are relative to the cwd on the given volume. #[inline] pub fn is_cwd_relative(&self) -> bool { self.prefix == Some(DiskPrefix) && !self.is_absolute() } /// Returns the PathPrefix for this Path #[inline] pub fn prefix(&self) -> Option<PathPrefix> { self.prefix } /// Returns whether the prefix is a verbatim prefix, i.e. ? #[inline] pub fn is_verbatim(&self) -> bool { prefix_is_verbatim(self.prefix) } fn equiv_prefix(&self, other: &Path) -> bool { match (self.prefix, other.prefix) { (Some(DiskPrefix), Some(VerbatimDiskPrefix)) => { self.is_absolute() && self.repr[0].to_ascii().eq_ignore_case(other.repr[4].to_ascii()) } (Some(VerbatimDiskPrefix), Some(DiskPrefix)) => { other.is_absolute() && self.repr[4].to_ascii().eq_ignore_case(other.repr[0].to_ascii()) } (Some(VerbatimDiskPrefix), Some(VerbatimDiskPrefix)) => { self.repr[4].to_ascii().eq_ignore_case(other.repr[4].to_ascii()) } (Some(UNCPrefix(_,_)), Some(VerbatimUNCPrefix(_,_))) => { self.repr.slice(2, self.prefix_len()) == other.repr.slice(8, other.prefix_len()) } (Some(VerbatimUNCPrefix(_,_)), Some(UNCPrefix(_,_))) => { self.repr.slice(8, self.prefix_len()) == other.repr.slice(2, other.prefix_len()) } (None, None) => true, (a, b) if a == b => { self.repr.slice_to(self.prefix_len()) == other.repr.slice_to(other.prefix_len()) } _ => false } } fn normalize_<S: Str>(s: S) -> (Option<PathPrefix>, ~str) { // make borrowck happy let (prefix, val) = { let prefix = parse_prefix(s.as_slice()); let path = Path::normalize__(s.as_slice(), prefix); (prefix, path) }; (prefix, match val { None => s.into_owned(), Some(val) => val }) } fn normalize__(s: &str, prefix: Option<PathPrefix>) -> Option<~str> { if prefix_is_verbatim(prefix) { // don't do any normalization match prefix { Some(VerbatimUNCPrefix(x, 0)) if s.len() == 8 + x => { // the server component has no trailing '' let mut s = s.into_owned(); s.push_char(sep); Some(s) } _ => None } } else { let (is_abs, comps) = normalize_helper(s, prefix); let mut comps = comps; match (comps.is_some(),prefix) { (false, Some(DiskPrefix)) => { if s[0] >= 'a' as u8 && s[0] <= 'z' as u8 { comps = Some(~[]); } } (false, Some(VerbatimDiskPrefix)) => { if s[4] >= 'a' as u8 && s[0] <= 'z' as u8 { comps = Some(~[]); } } _ => () } match comps { None => None, Some(comps) => { if prefix.is_some() && comps.is_empty() { match prefix.unwrap() { DiskPrefix => { let len = prefix_len(prefix) + is_abs as uint; let mut s = s.slice_to(len).to_owned(); s[0] = s[0].to_ascii().to_upper().to_byte(); if is_abs { s[2] = sep as u8; // normalize C:/ to C: } Some(s) } VerbatimDiskPrefix => { let len = prefix_len(prefix) + is_abs as uint; let mut s = s.slice_to(len).to_owned(); s[4] = s[4].to_ascii().to_upper().to_byte(); Some(s) } _ => { let plen = prefix_len(prefix); if s.len() > plen { Some(s.slice_to(plen).to_owned()) } else { None } } } } else if is_abs && comps.is_empty() { Some(str::from_char(sep)) } else { let prefix_ = s.slice_to(prefix_len(prefix)); let n = prefix_.len() + if is_abs { comps.len() } else { comps.len() - 1} + comps.iter().map(|v| v.len()).sum(); let mut s = str::with_capacity(n); match prefix { Some(DiskPrefix) => { s.push_char(prefix_[0].to_ascii().to_upper().to_char()); s.push_char(':'); } Some(VerbatimDiskPrefix) => { s.push_str(prefix_.slice_to(4)); s.push_char(prefix_[4].to_ascii().to_upper().to_char()); s.push_str(prefix_.slice_from(5)); } Some(UNCPrefix(a,b)) => { s.push_str(""); s.push_str(prefix_.slice(2, a+2)); s.push_char(sep); s.push_str(prefix_.slice(3+a, 3+a+b)); } Some(_) => s.push_str(prefix_), None => () } let mut it = comps.move_iter(); if !is_abs { match it.next() { None => (), Some(comp) => s.push_str(comp) } } for comp in it { s.push_char(sep); s.push_str(comp); } Some(s) } } } } } fn update_sepidx(&mut self) { let s = if self.has_nonsemantic_trailing_slash() { self.repr.slice_to(self.repr.len()-1) } else { self.repr.as_slice() }; let idx = s.rfind(if !prefix_is_verbatim(self.prefix) { is_sep2 } else { is_sep }); let prefixlen = self.prefix_len(); self.sepidx = idx.and_then(|x| if x < prefixlen { None } else { Some(x) }); } fn prefix_len(&self) -> uint { prefix_len(self.prefix) } // Returns a tuple (before, after, end) where before is the index of the separator // and after is the index just after the separator. // end is the length of the string, normally, or the index of the final character if it is // a non-semantic trailing separator in a verbatim string. // If the prefix is considered the separator, before and after are the same. fn sepidx_or_prefix_len(&self) -> Option<(uint,uint,uint)> { match self.sepidx { None => match self.prefix_len() { 0 => None, x => Some((x,x,self.repr.len())) }, Some(x) => { if self.has_nonsemantic_trailing_slash() { Some((x,x+1,self.repr.len()-1)) } else { Some((x,x+1,self.repr.len())) } } } } fn has_nonsemantic_trailing_slash(&self) -> bool { self.is_verbatim() && self.repr.len() > self.prefix_len()+1 && self.repr[self.repr.len()-1] == sep as u8 } fn update_normalized<S: Str>(&mut self, s: S) { let (prefix, path) = Path::normalize_(s); self.repr = path; self.prefix = prefix; self.update_sepidx(); } } /// The standard path separator character pub static sep: char = ''; /// The alternative path separator character pub static sep2: char = '/'; /// Returns whether the given byte is a path separator. /// Only allows the primary separator ''; use is_sep2 to allow '/'. #[inline] pub fn is_sep(c: char) -> bool { c == sep } /// Returns whether the given byte is a path separator. /// Allows both the primary separator '' and the alternative separator '/'. #[inline] pub fn is_sep2(c: char) -> bool { c == sep || c == sep2 } /// Prefix types for Path #[deriving(Eq, Clone, DeepClone)] pub enum PathPrefix { /// Prefix `?`, uint is the length of the following component VerbatimPrefix(uint), /// Prefix `?UNC`, uints are the lengths of the UNC components VerbatimUNCPrefix(uint, uint), /// Prefix `?C:` (for any alphabetic character) VerbatimDiskPrefix, /// Prefix `.`, uint is the length of the following component DeviceNSPrefix(uint), /// UNC prefix `servershare`, uints are the lengths of the server/share UNCPrefix(uint, uint), /// Prefix `C:` for any alphabetic character DiskPrefix } /// Internal function; only public for tests. Don't use. // FIXME (#8169): Make private once visibility is fixed pub fn parse_prefix<'a>(mut path: &'a str) -> Option<PathPrefix> { if path.starts_with("") { //  path = path.slice_from(2); if path.starts_with("?") { // ? path = path.slice_from(2); if path.starts_with("UNC") { // ?UNCservershare path = path.slice_from(4); let (idx_a, idx_b) = match parse_two_comps(path, is_sep) { Some(x) => x, None => (path.len(), 0) }; return Some(VerbatimUNCPrefix(idx_a, idx_b)); } else { // ?path let idx = path.find(''); if idx == Some(2) && path[1] == ':' as u8 { let c = path[0]; if c.is_ascii() && ::char::is_alphabetic(c as char) { // ?C: path return Some(VerbatimDiskPrefix); } } let idx = idx.unwrap_or(path.len()); return Some(VerbatimPrefix(idx)); } } else if path.starts_with(".") { // .path path = path.slice_from(2); let idx = path.find('').unwrap_or(path.len()); return Some(DeviceNSPrefix(idx)); } match parse_two_comps(path, is_sep2) { Some((idx_a, idx_b)) if idx_a > 0 && idx_b > 0 => { // servershare return Some(UNCPrefix(idx_a, idx_b)); } _ => () } } else if path.len() > 1 && path[1] == ':' as u8 { // C: let c = path[0]; if c.is_ascii() && ::char::is_alphabetic(c as char) { return Some(DiskPrefix); } } return None; fn parse_two_comps<'a>(mut path: &'a str, f: &fn(char)->bool) -> Option<(uint, uint)> { let idx_a = match path.find(|x| f(x)) { None => return None, Some(x) => x }; path = path.slice_from(idx_a+1); let idx_b = path.find(f).unwrap_or(path.len()); Some((idx_a, idx_b)) } } // None result means the string didn't need normalizing fn normalize_helper<'a>(s: &'a str, prefix: Option<PathPrefix>) -> (bool,Option<~[&'a str]>) { let f = if !prefix_is_verbatim(prefix) { is_sep2 } else { is_sep }; let is_abs = s.len() > prefix_len(prefix) && f(s.char_at(prefix_len(prefix))); let s_ = s.slice_from(prefix_len(prefix)); let s_ = if is_abs { s_.slice_from(1) } else { s_ }; if is_abs && s_.is_empty() { return (is_abs, match prefix { Some(DiskPrefix) | None => (if is_sep(s.char_at(prefix_len(prefix))) { None } else { Some(~[]) }), Some(_) => Some(~[]), // need to trim the trailing separator }); } let mut comps: ~[&'a str] = ~[]; let mut n_up = 0u; let mut changed = false; for comp in s_.split_iter(f) { if comp.is_empty() { changed = true } else if comp == "." { changed = true } else if comp == ".." { let has_abs_prefix = match prefix { Some(DiskPrefix) => false, Some(_) => true, None => false }; if (is_abs || has_abs_prefix) && comps.is_empty() { changed = true } else if comps.len() == n_up { comps.push(".."); n_up += 1 } else { comps.pop_opt(); changed = true } } else { comps.push(comp) } } if !changed && !prefix_is_verbatim(prefix) { changed = s.find(is_sep2).is_some(); } if changed { if comps.is_empty() && !is_abs && prefix.is_none() { if s == "." { return (is_abs, None); } comps.push("."); } (is_abs, Some(comps)) } else { (is_abs, 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) } fn prefix_is_verbatim(p: Option<PathPrefix>) -> bool { match p { Some(VerbatimPrefix(_)) | Some(VerbatimUNCPrefix(_,_)) | Some(VerbatimDiskPrefix) => true, Some(DeviceNSPrefix(_)) => true, // not really sure, but I think so _ => false } } fn prefix_len(p: Option<PathPrefix>) -> uint { match p { None => 0, Some(VerbatimPrefix(x)) => 4 + x, Some(VerbatimUNCPrefix(x,y)) => 8 + x + 1 + y, Some(VerbatimDiskPrefix) => 6, Some(UNCPrefix(x,y)) => 2 + x + 1 + y, Some(DeviceNSPrefix(x)) => 4 + x, Some(DiskPrefix) => 2 } } fn prefix_is_sep(p: Option<PathPrefix>, c: u8) -> bool { c.is_ascii() && if !prefix_is_verbatim(p) { is_sep2(c as char) } else { is_sep(c as char) } } #[cfg(test)] mod tests { use super::*; use option::{Some,None}; use iter::Iterator; 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_parse_prefix() { macro_rules! t( ($path:expr, $exp:expr) => ( { let path = $path; let exp = $exp; let res = parse_prefix(path); assert!(res == exp, "parse_prefix("%s"): expected %?, found %?", path, exp, res); } ) ) t!("SERVERsharefoo", Some(UNCPrefix(6,5))); t!("", None); t!("SERVER", None); t!("SERVER", None); t!("SERVER", None); t!("SERVERfoo", None); t!("SERVERshare", Some(UNCPrefix(6,5))); t!("SERVER/share/foo", Some(UNCPrefix(6,5))); t!("SERVERshare/foo", Some(UNCPrefix(6,5))); t!("//SERVER/share/foo", None); t!("abc", None); t!("?abc", Some(VerbatimPrefix(1))); t!("?a/b/c", Some(VerbatimPrefix(5))); t!("//?/a/b/c", None); t!(".ab", Some(DeviceNSPrefix(1))); t!(".a/b", Some(DeviceNSPrefix(3))); t!("//./a/b", None); t!("?UNCserversharefoo", Some(VerbatimUNCPrefix(6,5))); t!("?UNCsharefoo", Some(VerbatimUNCPrefix(0,5))); t!("?UNC", Some(VerbatimUNCPrefix(0,0))); t!("?UNCserver/share/foo", Some(VerbatimUNCPrefix(16,0))); t!("?UNCserver", Some(VerbatimUNCPrefix(6,0))); t!("?UNCserver", Some(VerbatimUNCPrefix(6,0))); t!("?UNC/server/share", Some(VerbatimPrefix(16))); t!("?UNC", Some(VerbatimPrefix(3))); t!("?C:ab.txt", Some(VerbatimDiskPrefix)); t!("?z:", Some(VerbatimDiskPrefix)); t!("?C:", Some(VerbatimPrefix(2))); t!("?C:a.txt", Some(VerbatimPrefix(7))); t!("?C:ab.txt", Some(VerbatimPrefix(3))); t!("?C:/a", Some(VerbatimPrefix(4))); t!("C:foo", Some(DiskPrefix)); t!("z:/foo", Some(DiskPrefix)); t!("d:", Some(DiskPrefix)); t!("ab:", None); t!("ü:foo", None); t!("3:foo", None); t!(" :foo", None); t!("::foo", None); t!("?C:", Some(VerbatimPrefix(2))); t!("?z:", Some(VerbatimDiskPrefix)); t!("?ab:", Some(VerbatimPrefix(3))); t!("?C:a", Some(VerbatimDiskPrefix)); t!("?C:/a", Some(VerbatimPrefix(4))); t!("?C:a/b", Some(VerbatimDiskPrefix)); } #[test] fn test_paths() { t!(v: Path::new([]), b!(".")); t!(v: Path::new(b!("")), b!("")); t!(v: Path::new(b!("abc")), b!("abc")); 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("hithere"), "hithere"); t!(s: Path::from_str("hithere.txt"), "hithere.txt"); t!(s: Path::from_str("/"), ""); 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"), "hithere"); t!(s: Path::from_str("hithere"), "hithere"); t!(s: Path::from_str("hi..there"), "there"); t!(s: Path::from_str("hi/../there"), "there"); t!(s: Path::from_str("..hithere"), "..hithere"); t!(s: Path::from_str("..hithere"), "hithere"); t!(s: Path::from_str("/../hi/there"), "hithere"); 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."), "hithere"); 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!("foobar")).into_vec(), b!("foobar").to_owned()); assert_eq!(Path::new(b!("foo....bar")).into_vec(), b!("bar").to_owned()); assert_eq!(Path::from_str("foobar").into_str(), Some(~"foobar")); assert_eq!(Path::from_str("foo....bar").into_str(), Some(~"bar")); t!(s: Path::from_str("a"), "a"); t!(s: Path::from_str("a"), "a"); t!(s: Path::from_str("ab"), "ab"); t!(s: Path::from_str("ab"), "ab"); t!(s: Path::from_str("ab/"), "ab"); t!(s: Path::from_str("b"), "b"); t!(s: Path::from_str("ab"), "ab"); t!(s: Path::from_str("abc"), "abc"); t!(s: Path::from_str("servershare/path"), "serversharepath"); t!(s: Path::from_str("server/share/path"), "serversharepath"); t!(s: Path::from_str("C:ab.txt"), "C:ab.txt"); t!(s: Path::from_str("C:a/b.txt"), "C:ab.txt"); t!(s: Path::from_str("z:ab.txt"), "Z:ab.txt"); t!(s: Path::from_str("z:/a/b.txt"), "Z:ab.txt"); t!(s: Path::from_str("ab:/a/b.txt"), "ab:ab.txt"); t!(s: Path::from_str("C:"), "C:"); t!(s: Path::from_str("C:"), "C:"); t!(s: Path::from_str("q:"), "Q:"); t!(s: Path::from_str("C:/"), "C:"); t!(s: Path::from_str("C:foo.."), "C:"); t!(s: Path::from_str("C:foo.."), "C:"); t!(s: Path::from_str("C:a"), "C:a"); t!(s: Path::from_str("C:a/"), "C:a"); t!(s: Path::from_str("C:ab"), "C:ab"); t!(s: Path::from_str("C:ab/"), "C:ab"); t!(s: Path::from_str("C:a"), "C:a"); t!(s: Path::from_str("C:a/"), "C:a"); t!(s: Path::from_str("C:ab"), "C:ab"); t!(s: Path::from_str("C:ab/"), "C:ab"); t!(s: Path::from_str("?z:ab.txt"), "?z:ab.txt"); t!(s: Path::from_str("?C:/a/b.txt"), "?C:/a/b.txt"); t!(s: Path::from_str("?C:a/b.txt"), "?C:a/b.txt"); t!(s: Path::from_str("?testab.txt"), "?testab.txt"); t!(s: Path::from_str("?foobar"), "?foobar"); t!(s: Path::from_str(".foobar"), ".foobar"); t!(s: Path::from_str("."), "."); t!(s: Path::from_str("?UNCserversharefoo"), "?UNCserversharefoo"); t!(s: Path::from_str("?UNCserver/share"), "?UNCserver/share"); t!(s: Path::from_str("?UNCserver"), "?UNCserver"); t!(s: Path::from_str("?UNC"), "?UNC"); t!(s: Path::from_str("?UNC"), "?UNC"); // I'm not sure whether .foo/bar should normalize to .foobar // as information is sparse and this isn't really googleable. // I'm going to err on the side of not normalizing it, as this skips the filesystem t!(s: Path::from_str(".foo/bar"), ".foo/bar"); t!(s: Path::from_str(".foobar"), ".foobar"); } #[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!("foobar", 0)); (b!("bar").to_owned()) }).inside { Path::new(b!("foobar", 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!("nullbyte").to_owned()) }).inside { p.set_dirname(b!("null", 0, "byte")); }; assert!(handled); assert_eq!(p.as_vec(), b!("nullbytefoo")); 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!("nullbytefoofoo")); } #[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() wnul" => { do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { Path::new(b!("foobar", 0)) }; }) t!(~"set_filename wnul" => { let mut p = Path::new(b!("foobar")); do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { p.set_filename(b!("foo", 0)) }; }) t!(~"set_dirname wnul" => { let mut p = Path::new(b!("foobar")); do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { p.set_dirname(b!("foo", 0)) }; }) t!(~"push wnul" => { let mut p = Path::new(b!("foobar")); do cond.trap(|_| { (b!("null", 0).to_owned()) }).inside { p.push(b!("foo", 0)) }; }) } #[test] #[should_fail] fn test_not_utf8_fail() { Path::new(b!("hello", 0x80, ".txt")); } #[test] fn test_components() { macro_rules! t( (s: $path:expr, $op:ident, $exp:expr) => ( { let path = Path::from_str($path); assert_eq!(path.$op(), Some($exp)); } ); (s: $path:expr, $op:ident, $exp:expr, opt) => ( { let path = Path::from_str($path); let left = path.$op(); assert_eq!(left, $exp); } ); (v: $path:expr, $op:ident, $exp:expr) => ( { let path = Path::new($path); assert_eq!(path.$op(), $exp); } ) ) t!(v: b!("abc"), filename, b!("c")); t!(s: "abc", filename_str, "c"); t!(s: "abc", filename_str, "c"); t!(s: "a", filename_str, "a"); t!(s: "a", filename_str, "a"); t!(s: ".", filename_str, ""); t!(s: "", filename_str, ""); t!(s: "..", filename_str, ""); t!(s: "....", filename_str, ""); t!(s: "c:foo.txt", filename_str, "foo.txt"); t!(s: "C:", filename_str, ""); t!(s: "C:", filename_str, ""); t!(s: "serversharefoo.txt", filename_str, "foo.txt"); t!(s: "servershare", filename_str, ""); t!(s: "server", filename_str, "server"); t!(s: "?barfoo.txt", filename_str, "foo.txt"); t!(s: "?bar", filename_str, ""); t!(s: "?", filename_str, ""); t!(s: "?UNCserversharefoo.txt", filename_str, "foo.txt"); t!(s: "?UNCserver", filename_str, ""); t!(s: "?UNC", filename_str, ""); t!(s: "?C:foo.txt", filename_str, "foo.txt"); t!(s: "?C:", filename_str, ""); t!(s: "?C:", filename_str, ""); t!(s: "?foo/bar", filename_str, ""); t!(s: "?C:/foo", filename_str, ""); t!(s: ".foobar", filename_str, "bar"); t!(s: ".foo", filename_str, ""); t!(s: ".foo/bar", filename_str, ""); t!(s: ".foobar/baz", filename_str, "bar/baz"); t!(s: ".", filename_str, ""); t!(s: "?ab", filename_str, "b"); t!(v: b!("abc"), dirname, b!("ab")); t!(s: "abc", dirname_str, "ab"); t!(s: "abc", dirname_str, "ab"); t!(s: "a", dirname_str, "."); t!(s: "a", dirname_str, ""); t!(s: ".", dirname_str, "."); t!(s: "", dirname_str, ""); t!(s: "..", dirname_str, ".."); t!(s: "....", dirname_str, "...."); t!(s: "c:foo.txt", dirname_str, "C:"); t!(s: "C:", dirname_str, "C:"); t!(s: "C:", dirname_str, "C:"); t!(s: "C:foo.txt", dirname_str, "C:"); t!(s: "serversharefoo.txt", dirname_str, "servershare"); t!(s: "servershare", dirname_str, "servershare"); t!(s: "server", dirname_str, ""); t!(s: "?barfoo.txt", dirname_str, "?bar"); t!(s: "?bar", dirname_str, "?bar"); t!(s: "?", dirname_str, "?"); t!(s: "?UNCserversharefoo.txt", dirname_str, "?UNCservershare"); t!(s: "?UNCserver", dirname_str, "?UNCserver"); t!(s: "?UNC", dirname_str, "?UNC"); t!(s: "?C:foo.txt", dirname_str, "?C:"); t!(s: "?C:", dirname_str, "?C:"); t!(s: "?C:", dirname_str, "?C:"); t!(s: "?C:/foo/bar", dirname_str, "?C:/foo/bar"); t!(s: "?foo/bar", dirname_str, "?foo/bar"); t!(s: ".foobar", dirname_str, ".foo"); t!(s: ".foo", dirname_str, ".foo"); t!(s: "?ab", dirname_str, "?a"); t!(v: b!("hithere.txt"), filestem, b!("there")); t!(s: "hithere.txt", filestem_str, "there"); t!(s: "hithere", filestem_str, "there"); t!(s: "there.txt", filestem_str, "there"); t!(s: "there", filestem_str, "there"); t!(s: ".", filestem_str, ""); t!(s: "", filestem_str, ""); t!(s: "foo.bar", filestem_str, ".bar"); t!(s: ".bar", filestem_str, ".bar"); t!(s: "..bar", filestem_str, "."); t!(s: "hithere..txt", filestem_str, "there."); t!(s: "..", filestem_str, ""); t!(s: "....", filestem_str, ""); // filestem is based on filename, so we don't need the full set of prefix tests t!(v: b!("hithere.txt"), extension, Some(b!("txt"))); t!(v: b!("hithere"), extension, None); t!(s: "hithere.txt", extension_str, Some("txt"), opt); t!(s: "hithere", extension_str, None, opt); t!(s: "there.txt", extension_str, Some("txt"), opt); t!(s: "there", extension_str, None, opt); t!(s: ".", extension_str, None, opt); t!(s: "", extension_str, None, opt); t!(s: "foo.bar", extension_str, None, opt); t!(s: ".bar", extension_str, None, opt); t!(s: "..bar", extension_str, Some("bar"), opt); t!(s: "hithere..txt", extension_str, Some("txt"), opt); t!(s: "..", extension_str, None, opt); t!(s: "....", extension_str, None, opt); // extension is based on filename, so we don't need the full set of prefix tests } #[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: "abc", ".."); t!(s: "abc", "d"); t!(s: "ab", "cd"); t!(s: "ab", "cd"); // this is just a sanity-check test. push_str and join_str share an implementation, // so there's no need for the full set of prefix tests // we do want to check one odd case though to ensure the prefix is re-parsed let mut p = Path::from_str("?C:"); assert_eq!(p.prefix(), Some(VerbatimPrefix(2))); p.push_str("foo"); assert_eq!(p.prefix(), Some(VerbatimDiskPrefix)); assert_eq!(p.as_str(), Some("?C:foo")); // and another with verbatim non-normalized paths let mut p = Path::from_str("?C:a"); p.push_str("foo"); assert_eq!(p.as_str(), Some("?C:afoo")); } #[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: "abc", "d", "abcd"); t!(s: "abc", "d", "abcd"); t!(s: "ab", "cd", "abcd"); t!(s: "ab", "cd", "cd"); t!(s: "ab", ".", "ab"); t!(s: "ab", "..c", "ac"); t!(s: "ab", "C:a.txt", "C:a.txt"); t!(s: "ab", "......c", "..c"); t!(s: "ab", "C:a.txt", "C:a.txt"); t!(s: "C:a", "C:b.txt", "C:b.txt"); t!(s: "C:abc", "C:d", "C:abcd"); t!(s: "C:abc", "C:d", "C:abcd"); t!(s: "C:ab", "......c", "C:..c"); t!(s: "C:ab", "......c", "C:c"); t!(s: "serversharefoo", "bar", "serversharefoobar"); t!(s: "serversharefoo", "....bar", "serversharebar"); t!(s: "serversharefoo", "C:baz", "C:baz"); t!(s: "?C:ab", "C:cd", "?C:abcd"); t!(s: "?C:ab", "C:cd", "C:cd"); t!(s: "?C:ab", "C:cd", "C:cd"); t!(s: "?foobar", "baz", "?foobarbaz"); t!(s: "?C:ab", "......c", "?C:ab......c"); t!(s: "?foobar", "....c", "?foobar....c"); t!(s: "?", "foo", "?foo"); t!(s: "?UNCserversharefoo", "bar", "?UNCserversharefoobar"); t!(s: "?UNCservershare", "C:a", "C:a"); t!(s: "?UNCservershare", "C:a", "C:a"); t!(s: "?UNCserver", "foo", "?UNCserverfoo"); t!(s: "C:a", "?UNCservershare", "?UNCservershare"); t!(s: ".foobar", "baz", ".foobarbaz"); t!(s: ".foobar", "C:a", "C:a"); // again, not sure about the following, but I'm assuming . should be verbatim t!(s: ".foo", "..bar", ".foo..bar"); t!(s: "?C:", "foo", "?C:foo"); // this is a weird one } #[test] fn test_pop() { macro_rules! t( (s: $path:expr, $left:expr, $right:expr) => ( { let pstr = $path; let mut p = Path::from_str(pstr); let file = p.pop_opt_str(); let left = $left; assert!(p.as_str() == Some(left), "`%s`.pop() failed; expected remainder `%s`, found `%s`", pstr, left, p.as_str().unwrap()); let right = $right; let res = file.map(|s| s.as_slice()); assert!(res == right, "`%s`.pop() failed; expected `%?`, found `%?`", pstr, right, res); } ); (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!(s: "abc", "ab", Some("c")); t!(s: "a", ".", Some("a")); t!(s: ".", ".", None); t!(s: "a", "", Some("a")); t!(s: "", "", None); t!(v: ["abc"], ["ab"], Some("c")); t!(v: ["a"], ["."], Some("a")); t!(v: ["."], ["."], None); t!(v: ["a"], [""], Some("a")); t!(v: [""], [""], None); t!(s: "C:ab", "C:a", Some("b")); t!(s: "C:a", "C:", Some("a")); t!(s: "C:", "C:", None); t!(s: "C:ab", "C:a", Some("b")); t!(s: "C:a", "C:", Some("a")); t!(s: "C:", "C:", None); t!(s: "servershareab", "serversharea", Some("b")); t!(s: "serversharea", "servershare", Some("a")); t!(s: "servershare", "servershare", None); t!(s: "?abc", "?ab", Some("c")); t!(s: "?ab", "?a", Some("b")); t!(s: "?a", "?a", None); t!(s: "?C:ab", "?C:a", Some("b")); t!(s: "?C:a", "?C:", Some("a")); t!(s: "?C:", "?C:", None); t!(s: "?UNCservershareab", "?UNCserversharea", Some("b")); t!(s: "?UNCserversharea", "?UNCservershare", Some("a")); t!(s: "?UNCservershare", "?UNCservershare", None); t!(s: ".abc", ".ab", Some("c")); t!(s: ".ab", ".a", Some("b")); t!(s: ".a", ".a", None); t!(s: "?ab", "?a", Some("b")); } #[test] fn test_join() { t!(s: Path::from_str("abc").join_str(".."), "ab"); t!(s: Path::from_str("abc").join_str("d"), "abcd"); t!(s: Path::from_str("ab").join_str("cd"), "abcd"); t!(s: Path::from_str("ab").join_str("cd"), "cd"); t!(s: Path::from_str(".").join_str("ab"), "ab"); t!(s: Path::from_str("").join_str("ab"), "ab"); t!(v: Path::new(b!("abc")).join(b!("..")), b!("ab")); t!(v: Path::new(b!("abc")).join(b!("d")), b!("abcd")); // full join testing is covered under test_push_path, so no need for // the full set of prefix tests } #[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: "abc", "..", "ab"); t!(s: "abc", "d", "abcd"); t!(s: "ab", "cd", "abcd"); t!(s: "ab", "cd", "cd"); t!(s: ".", "ab", "ab"); t!(s: "", "ab", "ab"); // join_path is implemented using push_path, so there's no need for // the full set of prefix tests } #[test] fn test_with_helpers() { macro_rules! t( (s: $path:expr, $op:ident, $arg:expr, $res:expr) => ( { let pstr = $path; let path = Path::from_str(pstr); let arg = $arg; let res = path.$op(arg); let exp = $res; assert!(res.as_str() == Some(exp), "`%s`.%s("%s"): Expected `%s`, found `%s`", pstr, stringify!($op), arg, exp, res.as_str().unwrap()); } ) ) t!(s: "abc", with_dirname_str, "d", "dc"); t!(s: "abc", with_dirname_str, "de", "dec"); t!(s: "abc", with_dirname_str, "", "c"); t!(s: "abc", with_dirname_str, "", "c"); t!(s: "abc", with_dirname_str, "/", "c"); t!(s: "abc", with_dirname_str, ".", "c"); t!(s: "abc", with_dirname_str, "..", "..c"); t!(s: "", with_dirname_str, "foo", "foo"); t!(s: "", with_dirname_str, "", "."); t!(s: "foo", with_dirname_str, "bar", "barfoo"); t!(s: "..", with_dirname_str, "foo", "foo"); t!(s: "....", with_dirname_str, "foo", "foo"); t!(s: "..", with_dirname_str, "", "."); t!(s: "....", with_dirname_str, "", "."); t!(s: ".", with_dirname_str, "foo", "foo"); t!(s: "foo", with_dirname_str, "..", "..foo"); t!(s: "foo", with_dirname_str, "....", "....foo"); t!(s: "C:ab", with_dirname_str, "foo", "foob"); t!(s: "foo", with_dirname_str, "C:ab", "C:abfoo"); t!(s: "C:ab", with_dirname_str, "servershare", "servershareb"); t!(s: "a", with_dirname_str, "servershare", "serversharea"); t!(s: "ab", with_dirname_str, "?", "?b"); t!(s: "ab", with_dirname_str, "C:", "C:b"); t!(s: "ab", with_dirname_str, "C:", "C:b"); t!(s: "ab", with_dirname_str, "C:/", "C:b"); t!(s: "C:", with_dirname_str, "foo", "foo"); t!(s: "C:", with_dirname_str, "foo", "foo"); t!(s: ".", with_dirname_str, "C:", "C:"); t!(s: ".", with_dirname_str, "C:/", "C:"); t!(s: "?C:foo", with_dirname_str, "C:", "C:foo"); t!(s: "?C:", with_dirname_str, "bar", "bar"); t!(s: "foobar", with_dirname_str, "?C:baz", "?C:bazbar"); t!(s: "?foo", with_dirname_str, "C:bar", "C:bar"); t!(s: "?afoo", with_dirname_str, "C:bar", "C:barfoo"); t!(s: "?afoo/bar", with_dirname_str, "C:baz", "C:bazfoobar"); t!(s: "?UNCserversharebaz", with_dirname_str, "a", "abaz"); t!(s: "foobar", with_dirname_str, "?UNCserversharebaz", "?UNCserversharebazbar"); t!(s: ".foo", with_dirname_str, "bar", "bar"); t!(s: ".foobar", with_dirname_str, "baz", "bazbar"); t!(s: ".foobar", with_dirname_str, "baz", "bazbar"); t!(s: ".foobar", with_dirname_str, "baz/", "bazbar"); t!(s: "abc", with_filename_str, "d", "abd"); t!(s: ".", with_filename_str, "foo", "foo"); t!(s: "abc", with_filename_str, "d", "abd"); t!(s: "", with_filename_str, "foo", "foo"); t!(s: "a", with_filename_str, "foo", "foo"); t!(s: "foo", with_filename_str, "bar", "bar"); t!(s: "", with_filename_str, "foo", "foo"); t!(s: "a", with_filename_str, "foo", "foo"); t!(s: "abc", with_filename_str, "", "ab"); t!(s: "abc", with_filename_str, ".", "ab"); t!(s: "abc", with_filename_str, "..", "a"); t!(s: "a", with_filename_str, "", ""); t!(s: "foo", with_filename_str, "", "."); t!(s: "abc", with_filename_str, "de", "abde"); t!(s: "abc", with_filename_str, "d", "abd"); t!(s: "..", with_filename_str, "foo", "..foo"); t!(s: "....", with_filename_str, "foo", "....foo"); t!(s: "..", with_filename_str, "", ".."); t!(s: "....", with_filename_str, "", "...."); t!(s: "C:foobar", with_filename_str, "baz", "C:foobaz"); t!(s: "C:foo", with_filename_str, "bar", "C:bar"); t!(s: "C:", with_filename_str, "foo", "C:foo"); t!(s: "C:foobar", with_filename_str, "baz", "C:foobaz"); t!(s: "C:foo", with_filename_str, "bar", "C:bar"); t!(s: "C:", with_filename_str, "foo", "C:foo"); t!(s: "C:foo", with_filename_str, "", "C:"); t!(s: "C:foo", with_filename_str, "", "C:"); t!(s: "C:foobar", with_filename_str, "..", "C:"); t!(s: "C:foo", with_filename_str, "..", "C:"); t!(s: "C:", with_filename_str, "..", "C:"); t!(s: "C:foobar", with_filename_str, "..", "C:"); t!(s: "C:foo", with_filename_str, "..", "C:.."); t!(s: "C:", with_filename_str, "..", "C:.."); t!(s: "serversharefoo", with_filename_str, "bar", "serversharebar"); t!(s: "servershare", with_filename_str, "foo", "serversharefoo"); t!(s: "serversharefoo", with_filename_str, "", "servershare"); t!(s: "servershare", with_filename_str, "", "servershare"); t!(s: "serversharefoo", with_filename_str, "..", "servershare"); t!(s: "servershare", with_filename_str, "..", "servershare"); t!(s: "?C:foobar", with_filename_str, "baz", "?C:foobaz"); t!(s: "?C:foo", with_filename_str, "bar", "?C:bar"); t!(s: "?C:", with_filename_str, "foo", "?C:foo"); t!(s: "?C:foo", with_filename_str, "..", "?C:.."); t!(s: "?foobar", with_filename_str, "baz", "?foobaz"); t!(s: "?foo", with_filename_str, "bar", "?foobar"); t!(s: "?", with_filename_str, "foo", "?foo"); t!(s: "?foobar", with_filename_str, "..", "?foo.."); t!(s: ".foobar", with_filename_str, "baz", ".foobaz"); t!(s: ".foo", with_filename_str, "bar", ".foobar"); t!(s: ".foobar", with_filename_str, "..", ".foo.."); t!(s: "hithere.txt", with_filestem_str, "here", "hihere.txt"); t!(s: "hithere.txt", with_filestem_str, "", "hi.txt"); t!(s: "hithere.txt", with_filestem_str, ".", "hi..txt"); t!(s: "hithere.txt", with_filestem_str, "..", "hi...txt"); t!(s: "hithere.txt", with_filestem_str, "", "hi.txt"); t!(s: "hithere.txt", with_filestem_str, "foobar", "hifoobar.txt"); t!(s: "hithere.foo.txt", with_filestem_str, "here", "hihere.txt"); t!(s: "hithere", with_filestem_str, "here", "hihere"); t!(s: "hithere", with_filestem_str, "", "hi"); t!(s: "hi", with_filestem_str, "", "."); t!(s: "hi", with_filestem_str, "", ""); t!(s: "hithere", with_filestem_str, "..", "."); t!(s: "hithere", with_filestem_str, ".", "hi"); t!(s: "hithere.", with_filestem_str, "foo", "hifoo."); t!(s: "hithere.", with_filestem_str, "", "hi"); t!(s: "hithere.", with_filestem_str, ".", "."); t!(s: "hithere.", with_filestem_str, "..", "hi..."); t!(s: "", with_filestem_str, "foo", "foo"); t!(s: ".", with_filestem_str, "foo", "foo"); t!(s: "hithere..", with_filestem_str, "here", "hihere."); t!(s: "hithere..", with_filestem_str, "", "hi"); // filestem setter calls filename setter internally, no need for extended tests t!(s: "hithere.txt", with_extension_str, "exe", "hithere.exe"); t!(s: "hithere.txt", with_extension_str, "", "hithere"); t!(s: "hithere.txt", with_extension_str, ".", "hithere.."); t!(s: "hithere.txt", with_extension_str, "..", "hithere..."); t!(s: "hithere", with_extension_str, "txt", "hithere.txt"); t!(s: "hithere", with_extension_str, ".", "hithere.."); t!(s: "hithere", with_extension_str, "..", "hithere..."); t!(s: "hithere.", with_extension_str, "txt", "hithere.txt"); t!(s: "hi.foo", with_extension_str, "txt", "hi.foo.txt"); t!(s: "hithere.txt", with_extension_str, ".foo", "hithere..foo"); t!(s: "", with_extension_str, "txt", ""); t!(s: "", with_extension_str, ".", ""); t!(s: "", with_extension_str, "..", ""); t!(s: ".", with_extension_str, "txt", "."); // extension setter calls filename setter internally, no need for extended tests } #[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!("abc"), set_dirname, with_dirname, b!("d")); t!(v: b!("abc"), set_dirname, with_dirname, b!("de")); t!(s: "abc", set_dirname_str, with_dirname_str, "d"); t!(s: "abc", set_dirname_str, with_dirname_str, "de"); t!(s: "", set_dirname_str, with_dirname_str, "foo"); t!(s: "foo", set_dirname_str, with_dirname_str, "bar"); t!(s: "abc", 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!("abc"), set_filename, with_filename, b!("d")); t!(v: b!(""), set_filename, with_filename, b!("foo")); t!(s: "abc", 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: "ab", set_filename_str, with_filename_str, ""); t!(s: "a", set_filename_str, with_filename_str, ""); t!(v: b!("hithere.txt"), set_filestem, with_filestem, b!("here")); t!(s: "hithere.txt", set_filestem_str, with_filestem_str, "here"); t!(s: "hithere.", set_filestem_str, with_filestem_str, "here"); t!(s: "hithere", set_filestem_str, with_filestem_str, "here"); t!(s: "hithere.txt", set_filestem_str, with_filestem_str, ""); t!(s: "hithere", set_filestem_str, with_filestem_str, ""); t!(v: b!("hithere.txt"), set_extension, with_extension, b!("exe")); t!(s: "hithere.txt", set_extension_str, with_extension_str, "exe"); t!(s: "hithere.", set_extension_str, with_extension_str, "txt"); t!(s: "hithere", set_extension_str, with_extension_str, "txt"); t!(s: "hithere.txt", set_extension_str, with_extension_str, ""); t!(s: "hithere", set_extension_str, with_extension_str, ""); t!(s: ".", set_extension_str, with_extension_str, "txt"); // with_ helpers use the setter internally, so the tests for the with_ helpers // will suffice. No need for the full set of prefix tests. } #[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!("abc")), b!("c"), b!("ab"), b!("c"), None); t!(s: Path::from_str("abc"), Some("c"), Some("ab"), 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("hithere.txt"), Some("there.txt"), Some("hi"), Some("there"), Some("txt")); t!(s: Path::from_str("hithere"), Some("there"), Some("hi"), Some("there"), None); t!(s: Path::from_str("hithere."), 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")); // these are already tested in test_components, so no need for extended tests } #[test] fn test_dir_file_path() { t!(s: Path::from_str("hithere").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( ($path:expr, $exp:expr) => ( { let path = $path; let left = path.and_then_ref(|p| p.as_str()); assert_eq!(left, $exp); } ); ) t!(Path::from_str("hithere").file_path(), Some("there")); t!(Path::from_str("hi").file_path(), Some("hi")); t!(Path::from_str(".").file_path(), None); t!(Path::from_str("").file_path(), None); t!(Path::from_str("..").file_path(), None); t!(Path::from_str("....").file_path(), None); // dir_path and file_path are just dirname and filename interpreted as paths. // No need for extended tests } #[test] fn test_is_absolute() { macro_rules! t( ($path:expr, $abs:expr, $vol:expr, $cwd:expr) => ( { let path = Path::from_str($path); let (abs, vol, cwd) = ($abs, $vol, $cwd); let b = path.is_absolute(); assert!(b == abs, "Path '%s'.is_absolute(): expected %?, found %?", path.as_str().unwrap(), abs, b); let b = path.is_vol_relative(); assert!(b == vol, "Path '%s'.is_vol_relative(): expected %?, found %?", path.as_str().unwrap(), vol, b); let b = path.is_cwd_relative(); assert!(b == cwd, "Path '%s'.is_cwd_relative(): expected %?, found %?", path.as_str().unwrap(), cwd, b); } ) ) t!("abc", false, false, false); t!("abc", false, true, false); t!("a", false, false, false); t!("a", false, true, false); t!(".", false, false, false); t!("", false, true, false); t!("..", false, false, false); t!("....", false, false, false); t!("C:ab.txt", false, false, true); t!("C:ab.txt", true, false, false); t!("servershareab.txt", true, false, false); t!("?abc.txt", true, false, false); t!("?C:ab.txt", true, false, false); t!("?C:ab.txt", true, false, false); // NB: not equivalent to C:ab.txt t!("?UNCservershareab.txt", true, false, false); t!(".ab", true, false, 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); let exp = $exp; let res = path.is_ancestor_of(&dest); assert!(res == exp, "`%s`.is_ancestor_of(`%s`): Expected %?, found %?", path.as_str().unwrap(), dest.as_str().unwrap(), exp, res); } ) ) t!(s: "abc", "abcd", true); t!(s: "abc", "abc", true); t!(s: "abc", "ab", false); t!(s: "abc", "abc", true); t!(s: "ab", "abc", true); t!(s: "abcd", "abc", false); t!(s: "ab", "abc", false); t!(s: "ab", "abc", false); t!(s: "abc", "abd", false); t!(s: "..abc", "abc", false); t!(s: "abc", "..abc", false); t!(s: "abc", "abcd", false); t!(s: "abcd", "abc", false); t!(s: "..ab", "..abc", true); t!(s: ".", "ab", true); t!(s: ".", ".", true); t!(s: "", "", true); t!(s: "", "ab", true); t!(s: "..", "ab", true); t!(s: "....", "ab", true); t!(s: "foobar", "foobar", false); t!(s: "foobar", "foobar", false); t!(s: "foo", "C:foo", false); t!(s: "C:foo", "foo", false); t!(s: "C:foo", "C:foobar", true); t!(s: "C:foobar", "C:foo", false); t!(s: "C:foo", "C:foobar", true); t!(s: "C:", "C:", true); t!(s: "C:", "C:", false); t!(s: "C:", "C:", false); t!(s: "C:", "C:", true); t!(s: "C:foobar", "C:foo", false); t!(s: "C:foobar", "C:foo", false); t!(s: "C:foo", "foo", false); t!(s: "foo", "C:foo", false); t!(s: "serversharefoo", "serversharefoobar", true); t!(s: "servershare", "serversharefoo", true); t!(s: "serversharefoo", "servershare", false); t!(s: "C:foo", "serversharefoo", false); t!(s: "serversharefoo", "C:foo", false); t!(s: "?foobar", "?foobarbaz", true); t!(s: "?foobarbaz", "?foobar", false); t!(s: "?foobar", "foobarbaz", false); t!(s: "foobar", "?foobarbaz", false); t!(s: "?C:foobar", "?C:foobarbaz", true); t!(s: "?C:foobarbaz", "?C:foobar", false); t!(s: "?C:", "?C:foo", true); t!(s: "?C:", "?C:", false); // this is a weird one t!(s: "?C:", "?C:", false); t!(s: "?C:a", "?c:ab", true); t!(s: "?c:a", "?C:ab", true); t!(s: "?C:a", "?D:ab", false); t!(s: "?foo", "?foobar", false); t!(s: "?ab", "?abc", true); t!(s: "?ab", "?ab", true); t!(s: "?ab", "?ab", true); t!(s: "?abc", "?ab", false); t!(s: "?abc", "?ab", false); t!(s: "?UNCabc", "?UNCabcd", true); t!(s: "?UNCabcd", "?UNCabc", false); t!(s: "?UNCab", "?UNCabc", true); t!(s: ".foobar", ".foobarbaz", true); t!(s: ".foobarbaz", ".foobar", false); t!(s: ".foo", ".foobar", true); t!(s: ".foo", ".foobar", false); t!(s: "ab", "?ab", false); t!(s: "?ab", "ab", false); t!(s: "ab", "?C:ab", false); t!(s: "?C:ab", "ab", false); t!(s: "Z:ab", "?z:ab", true); t!(s: "C:ab", "?D:ab", false); t!(s: "ab", "?ab", false); t!(s: "?ab", "ab", false); t!(s: "C:ab", "?C:ab", true); t!(s: "?C:ab", "C:ab", true); t!(s: "C:ab", "?C:ab", false); t!(s: "C:ab", "?C:ab", false); t!(s: "?C:ab", "C:ab", false); t!(s: "?C:ab", "C:ab", false); t!(s: "C:ab", "?C:ab", true); t!(s: "?C:ab", "C:ab", true); t!(s: "abc", "?UNCabc", true); t!(s: "?UNCabc", "abc", 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); let exp = $exp; assert!(res.and_then_ref(|x| x.as_str()) == exp, "`%s`.path_relative_from(`%s`): Expected %?, got %?", path.as_str().unwrap(), other.as_str().unwrap(), exp, res.and_then_ref(|x| x.as_str())); } ) ) t!(s: "abc", "ab", Some("c")); t!(s: "abc", "abd", Some("..c")); t!(s: "abc", "abcd", Some("..")); t!(s: "abc", "abc", Some(".")); t!(s: "abc", "abcde", Some("....")); t!(s: "abc", "ade", Some("....bc")); t!(s: "abc", "def", Some("......abc")); t!(s: "abc", "abc", None); t!(s: "abc", "abc", Some("abc")); t!(s: "abc", "abcd", Some("..")); t!(s: "abc", "ab", Some("c")); t!(s: "abc", "abcde", Some("....")); t!(s: "abc", "ade", Some("....bc")); t!(s: "abc", "def", Some("......abc")); t!(s: "hithere.txt", "hithere", Some("..there.txt")); t!(s: ".", "a", Some("..")); t!(s: ".", "ab", Some("....")); t!(s: ".", ".", Some(".")); t!(s: "a", ".", Some("a")); t!(s: "ab", ".", Some("ab")); t!(s: "..", ".", Some("..")); t!(s: "abc", "abc", Some(".")); t!(s: "abc", "abc", 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", "....ab", Some("..")); t!(s: "....ab", "....a", Some("b")); t!(s: "C:abc", "C:ab", Some("c")); t!(s: "C:ab", "C:abc", Some("..")); t!(s: "C:" ,"C:ab", Some("....")); t!(s: "C:ab", "C:cd", Some("....ab")); t!(s: "C:ab", "D:cd", Some("C:ab")); t!(s: "C:ab", "C:..c", None); t!(s: "C:..a", "C:bc", Some("......a")); t!(s: "C:abc", "C:ab", Some("c")); t!(s: "C:ab", "C:abc", Some("..")); t!(s: "C:", "C:ab", Some("....")); t!(s: "C:ab", "C:cd", Some("....ab")); t!(s: "C:ab", "C:ab", Some("C:ab")); t!(s: "C:ab", "C:ab", None); t!(s: "ab", "C:ab", None); t!(s: "ab", "C:ab", None); t!(s: "ab", "C:ab", None); t!(s: "ab", "C:ab", None); t!(s: "abc", "ab", Some("c")); t!(s: "ab", "abc", Some("..")); t!(s: "abce", "abcd", Some("..e")); t!(s: "acd", "abd", Some("acd")); t!(s: "bcd", "acd", Some("bcd")); t!(s: "abc", "de", Some("abc")); t!(s: "de", "abc", None); t!(s: "de", "abc", None); t!(s: "C:abc", "abc", Some("C:abc")); t!(s: "C:c", "abc", Some("C:c")); t!(s: "?ab", "ab", Some("?ab")); t!(s: "?ab", "ab", Some("?ab")); t!(s: "?ab", "b", Some("?ab")); t!(s: "?ab", "b", Some("?ab")); t!(s: "?ab", "?abc", Some("..")); t!(s: "?abc", "?ab", Some("c")); t!(s: "?ab", "?cd", Some("?ab")); t!(s: "?a", "?b", Some("?a")); t!(s: "?C:ab", "?C:a", Some("b")); t!(s: "?C:a", "?C:ab", Some("..")); t!(s: "?C:a", "?C:b", Some("..a")); t!(s: "?C:a", "?D:a", Some("?C:a")); t!(s: "?C:ab", "?c:a", Some("b")); t!(s: "?C:ab", "C:a", Some("b")); t!(s: "?C:a", "C:ab", Some("..")); t!(s: "C:ab", "?C:a", Some("b")); t!(s: "C:a", "?C:ab", Some("..")); t!(s: "?C:a", "D:a", Some("?C:a")); t!(s: "?c:ab", "C:a", Some("b")); t!(s: "?C:ab", "C:ab", Some("?C:ab")); t!(s: "?C:a.b", "C:a", Some("?C:a.b")); t!(s: "?C:ab/c", "C:a", Some("?C:ab/c")); t!(s: "?C:a..b", "C:a", Some("?C:a..b")); t!(s: "C:ab", "?C:ab", None); t!(s: "?C:a.b", "?C:a", Some("?C:a.b")); t!(s: "?C:ab/c", "?C:a", Some("?C:ab/c")); t!(s: "?C:a..b", "?C:a", Some("?C:a..b")); t!(s: "?C:ab", "?C:a", Some("b")); t!(s: "?C:.b", "?C:.", Some("b")); t!(s: "C:b", "?C:.", Some("..b")); t!(s: "?a.bc", "?a.b", Some("c")); t!(s: "?abc", "?a.d", Some("....bc")); t!(s: "?a..b", "?a..", Some("b")); t!(s: "?ab..", "?ab", Some("?ab..")); t!(s: "?abc", "?a..b", Some("....bc")); t!(s: "?UNCabc", "?UNCab", Some("c")); t!(s: "?UNCab", "?UNCabc", Some("..")); t!(s: "?UNCabc", "?UNCacd", Some("?UNCabc")); t!(s: "?UNCbcd", "?UNCacd", Some("?UNCbcd")); t!(s: "?UNCabc", "?abc", Some("?UNCabc")); t!(s: "?UNCabc", "?C:abc", Some("?UNCabc")); t!(s: "?UNCabc/d", "?UNCab", Some("?UNCabc/d")); t!(s: "?UNCab.", "?UNCab", Some("?UNCab.")); t!(s: "?UNCab..", "?UNCab", Some("?UNCab..")); t!(s: "?UNCabc", "ab", Some("c")); t!(s: "?UNCab", "abc", Some("..")); t!(s: "?UNCabc", "acd", Some("?UNCabc")); t!(s: "?UNCbcd", "acd", Some("?UNCbcd")); t!(s: "?UNCab.", "ab", Some("?UNCab.")); t!(s: "?UNCabc/d", "ab", Some("?UNCabc/d")); t!(s: "?UNCab..", "ab", Some("?UNCab..")); t!(s: "abc", "?UNCab", Some("c")); t!(s: "abc", "?UNCacd", Some("abc")); } #[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; assert_eq!(comps.as_slice(), exp); } ); (v: [$($arg:expr),+], $exp:expr) => ( { let path = Path::new(b!($($arg),+)); let comps = path.component_iter().to_owned_vec(); let exp: &[&str] = $exp; assert_eq!(comps.as_slice(), exp); } ) ) t!(v: ["abc"], ["a", "b", "c"]); t!(s: "abc", ["a", "b", "c"]); t!(s: "abd", ["a", "b", "d"]); t!(s: "abcd", ["a", "b", "cd"]); t!(s: "abc", ["a", "b", "c"]); t!(s: "a", ["a"]); t!(s: "a", ["a"]); t!(s: "", []); t!(s: ".", ["."]); t!(s: "..", [".."]); t!(s: "....", ["..", ".."]); t!(s: "....foo", ["..", "..", "foo"]); t!(s: "C:foobar", ["foo", "bar"]); t!(s: "C:foo", ["foo"]); t!(s: "C:", []); t!(s: "C:foobar", ["foo", "bar"]); t!(s: "C:foo", ["foo"]); t!(s: "C:", []); t!(s: "serversharefoobar", ["foo", "bar"]); t!(s: "serversharefoo", ["foo"]); t!(s: "servershare", []); t!(s: "?foobarbaz", ["bar", "baz"]); t!(s: "?foobar", ["bar"]); t!(s: "?foo", []); t!(s: "?", []); t!(s: "?ab", ["b"]); t!(s: "?ab", ["b"]); t!(s: "?foobarbaz", ["bar", "", "baz"]); t!(s: "?C:foobar", ["foo", "bar"]); t!(s: "?C:foo", ["foo"]); t!(s: "?C:", []); t!(s: "?C:foo", ["foo"]); t!(s: "?UNCserversharefoobar", ["foo", "bar"]); t!(s: "?UNCserversharefoo", ["foo"]); t!(s: "?UNCservershare", []); t!(s: ".foobarbaz", ["bar", "baz"]); t!(s: ".foobar", ["bar"]); t!(s: ".foo", []); } } </ins>


pub mod c_str; pub mod os; pub mod path; <ins> pub mod path2; </ins> pub mod rand; pub mod run; pub mod sys;


// check that a comma comes after every field if !ate_comma { <del> let err = ExpectedCommaAfterPatternField { span: self.token.span } </del> <ins> let mut err = ExpectedCommaAfterPatternField { span: self.token.span } </ins> .into_diagnostic(&self.sess.span_diagnostic); if let Some(mut delayed) = delayed_err { delayed.emit(); } <ins> self.recover_misplaced_pattern_modifiers(&fields, &mut err); </ins> return Err(err); } ate_comma = false;


Ok((fields, etc)) } <ins> /// If the user writes `S { ref field: name }` instead of `S { field: ref name }`, we suggest /// the correct code. fn recover_misplaced_pattern_modifiers( &self, fields: &ThinVec<PatField>, err: &mut DiagnosticBuilder<'a, ErrorGuaranteed>, ) { if let Some(last) = fields.iter().last() && last.is_shorthand && let PatKind::Ident(binding, ident, None) = last.pat.kind && binding != BindingAnnotation::NONE && self.token == token::Colon // We found `ref mut? ident:`, try to parse a `name,` or `name }`. && let Some(name_span) = self.look_ahead(1, |t| t.is_ident().then(|| t.span)) && self.look_ahead(2, |t| { t == &token::Comma || t == &token::CloseDelim(Delimiter::Brace) }) { let span = last.pat.span.with_hi(ident.span.lo()); // We have `S { ref field: name }` instead of `S { field: ref name }` err.multipart_suggestion( "the pattern modifiers belong after the `:`", vec![ (span, String::new()), (name_span.shrink_to_lo(), binding.prefix_str().to_string()), ], Applicability::MachineApplicable, ); } } </ins> /// Recover on `...` or `_` as if it were `..` to avoid further errors. /// See issue #46718. fn recover_bad_dot_dot(&self) {


<ins> // run-rustfix struct S { field_name: (), } fn main() { match (S {field_name: ()}) { S {field_name: ref _foo} => {} //~ ERROR expected `,` } match (S {field_name: ()}) { S {field_name: mut _foo} => {} //~ ERROR expected `,` } match (S {field_name: ()}) { S {field_name: ref mut _foo} => {} //~ ERROR expected `,` } // Verify that we recover enough to run typeck. let _: usize = 3usize; //~ ERROR mismatched types } </ins>


<ins> // run-rustfix struct S { field_name: (), } fn main() { match (S {field_name: ()}) { S {ref field_name: _foo} => {} //~ ERROR expected `,` } match (S {field_name: ()}) { S {mut field_name: _foo} => {} //~ ERROR expected `,` } match (S {field_name: ()}) { S {ref mut field_name: _foo} => {} //~ ERROR expected `,` } // Verify that we recover enough to run typeck. let _: usize = 3u8; //~ ERROR mismatched types } </ins>


<ins> error: expected `,` --> $DIR/incorrect-placement-of-pattern-modifiers.rs:8:26 | LL |         S {ref field_name: _foo} => {} |         -                ^ |         | |         while parsing the fields for this pattern | help: the pattern modifiers belong after the `:` | LL -         S {ref field_name: _foo} => {} LL +         S {field_name: ref _foo} => {} | error: expected `,` --> $DIR/incorrect-placement-of-pattern-modifiers.rs:11:26 | LL |         S {mut field_name: _foo} => {} |         -                ^ |         | |         while parsing the fields for this pattern | help: the pattern modifiers belong after the `:` | LL -         S {mut field_name: _foo} => {} LL +         S {field_name: mut _foo} => {} | error: expected `,` --> $DIR/incorrect-placement-of-pattern-modifiers.rs:14:30 | LL |         S {ref mut field_name: _foo} => {} |         -                    ^ |         | |         while parsing the fields for this pattern | help: the pattern modifiers belong after the `:` | LL -         S {ref mut field_name: _foo} => {} LL +         S {field_name: ref mut _foo} => {} | error[E0308]: mismatched types --> $DIR/incorrect-placement-of-pattern-modifiers.rs:17:20 | LL |     let _: usize = 3u8; |            -----   ^^^ expected `usize`, found `u8` |            | |            expected due to this | help: change the type of the numeric literal from `u8` to `usize` | LL |     let _: usize = 3usize; |                     ~~~~~ error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0308`. </ins>


) )] #[doc(alias = "?")] <ins> #[cfg_attr(not(bootstrap), lang = "try")] </ins> pub trait Try { /// The type of this value when viewed as successful. #[unstable(feature = "try_trait", issue = "42327")]


AlignOffsetLangItem,         "align_offset",       align_offset_fn,         Target::Fn; TerminationTraitLangItem,    "termination",        termination,             Target::Trait; <ins> TryTraitLangItem,            "try",                try_trait,               Target::Trait; </ins> }


self.suggest_remove_reference(&obligation, &mut err, &trait_ref); self.suggest_semicolon_removal(&obligation, &mut err, span, &trait_ref); self.note_version_mismatch(&mut err, &trait_ref); <del> self.suggest_await_before_try(&mut err, &obligation, &trait_ref, span); </del> <ins> if Some(trait_ref.def_id()) == tcx.lang_items().try_trait() { self.suggest_await_before_try(&mut err, &obligation, &trait_ref, span); } </ins> if self.suggest_impl_trait(&mut err, span, &obligation, &trait_ref) { err.emit(); return;


<ins> // edition:2018 // compile-flags:-Cincremental=tmp/issue-72442 use std::fs::File; use std::future::Future; use std::io::prelude::*; fn main() -> Result<(), Box<dyn std::error::Error>> { block_on(async { { let path = std::path::Path::new("."); let mut f = File::open(path.to_str())?; //~^ ERROR the trait bound let mut src = String::new(); f.read_to_string(&mut src)?; Ok(()) } }) } fn block_on<F>(f: F) -> F::Output where F: Future<Output = Result<(), Box<dyn std::error::Error>>>, { Ok(()) } </ins>


<ins> error[E0277]: the trait bound `std::option::Option<&str>: std::convert::AsRef<std::path::Path>` is not satisfied --> $DIR/issue-72442.rs:12:36 | LL |             let mut f = File::open(path.to_str())?; |                                    ^^^^^^^^^^^^^ the trait `std::convert::AsRef<std::path::Path>` is not implemented for `std::option::Option<&str>` | ::: $SRC_DIR/libstd/fs.rs:LL:COL | LL |     pub fn open<P: AsRef<Path>>(path: P) -> io::Result<File> { |                    ----------- required by this bound in `std::fs::File::open` error: aborting due to previous error For more information about this error, try `rustc --explain E0277`. </ins>


return Some(*c); } <del> let mut dist = dist.unwrap_or_else(|| cmp::max(lookup.len(), 3) / 3); </del> <ins> // `fn edit_distance()` use `chars()` to calculate edit distance, so we must // also use `chars()` (and not `str::len()`) to calculate length here. let lookup_len = lookup.chars().count(); let mut dist = dist.unwrap_or_else(|| cmp::max(lookup_len, 3) / 3); </ins> let mut best = None; // store the candidates with the same distance, only for `use_substring_score` current. let mut next_candidates = vec![];


<ins> fn main() { // There shall be no suggestions here. In particular not `Ok`. let _ = 读文; //~ ERROR cannot find value `读文` in this scope } </ins>


<ins> error[E0425]: cannot find value `读文` in this scope --> $DIR/non_ascii_ident.rs:3:13 | LL |     let _ = 读文; |             ^^^^ not found in this scope error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0425`. </ins>


fn default() -> Self; } <del> /// Return the default value of a type according to the `Default` trait. /// /// The type to return is inferred from context; this is equivalent to /// `Default::default()` but shorter to type. /// /// For example: /// ``` /// #![feature(default_free_fn)] /// /// use std::default::default; /// /// #[derive(Default)] /// struct AppConfig { ///     foo: FooConfig, ///     bar: BarConfig, /// } /// /// #[derive(Default)] /// struct FooConfig { ///     foo: i32, /// } /// /// #[derive(Default)] /// struct BarConfig { ///     bar: f32, ///     baz: u8, /// } /// /// fn main() { ///     let options = AppConfig { ///         foo: default(), ///         bar: BarConfig { ///             bar: 10.1, ///             ..default() ///         }, ///     }; /// } /// ``` #[unstable(feature = "default_free_fn", issue = "73014")] #[must_use] #[inline] pub fn default<T: Default>() -> T { Default::default() } </del> /// Derive macro generating an impl of the trait `Default`. #[rustc_builtin_macro(Default, attributes(default))] #[stable(feature = "builtin_macro_prelude", since = "1.38.0")]


<del> # `default_free_fn` The tracking issue for this feature is: [#73014] [#73014]: https://github.com/rust-lang/rust/issues/73014 ------------------------ Adds a free `default()` function to the `std::default` module.  This function just forwards to [`Default::default()`], but may remove repetition of the word "default" from the call site. [`Default::default()`]: ../../std/default/trait.Default.html#tymethod.default Here is an example: ```rust #![feature(default_free_fn)] use std::default::default; #[derive(Default)] struct AppConfig { foo: FooConfig, bar: BarConfig, } #[derive(Default)] struct FooConfig { foo: i32, } #[derive(Default)] struct BarConfig { bar: f32, baz: u8, } fn main() { let options = AppConfig { foo: default(), bar: BarConfig { bar: 10.1, ..default() }, }; } ``` </del>


<del> error[E0425]: cannot find function `default` in this scope --> $DIR/issue-2356.rs:31:5 | LL |     default(); |     ^^^^^^^ | help: you might have meant to call the associated function | LL |     Self::default(); |     ~~~~~~~~~~~~~ help: consider importing this function | LL + use std::default::default; | </del> error[E0425]: cannot find value `whiskers` in this scope --> $DIR/issue-2356.rs:39:5 |


LL |     clone(); |     ^^^^^ help: you might have meant to call the method: `self.clone` <ins> error[E0425]: cannot find function `default` in this scope --> $DIR/issue-2356.rs:31:5 | LL |     default(); |     ^^^^^^^ help: you might have meant to call the associated function: `Self::default` </ins> error[E0425]: cannot find function `shave` in this scope --> $DIR/issue-2356.rs:41:5 |


// As write_sub_paths, but does not process the last ident in the path (assuming it // will be processed elsewhere). See note on write_sub_paths about global. fn write_sub_paths_truncated(&mut self, path: &'tcx hir::Path<'tcx>) { <del> for seg in &path.segments[..path.segments.len() - 1] { if let Some(data) = self.save_ctxt.get_path_segment_data(seg) { self.dumper.dump_ref(data); </del> <ins> if let [segments @ .., _] = path.segments { for seg in segments { if let Some(data) = self.save_ctxt.get_path_segment_data(seg) { self.dumper.dump_ref(data); } </ins> } } }


} } } <del> hir::ExprKind::Struct(hir::QPath::Resolved(_, path), ..) => { </del> <ins> hir::ExprKind::Struct(qpath, ..) => { let segment = match qpath { hir::QPath::Resolved(_, path) => path.segments.last().unwrap(), hir::QPath::TypeRelative(_, segment) => segment, }; </ins> match self.tables.expr_ty_adjusted(&hir_node).kind { ty::Adt(def, _) if !def.is_enum() => { <del> let sub_span = path.segments.last().unwrap().ident.span; </del> <ins> let sub_span = segment.ident.span; </ins> filter!(self.span_utils, sub_span); let span = self.span_from_span(sub_span); Some(Data::RefData(Ref {


} _ => { // FIXME <del> bug!(); </del> <ins> bug!("invalid expression: {:?}", expr); </ins> } } }


<ins> // compile-flags: -Zsave-analysis use {self}; //~ ERROR E0431 fn main () { } </ins>


<ins> error[E0431]: `self` import can only appear in an import list with a non-empty prefix --> $DIR/issue-73020.rs:2:6 | LL | use {self}; |      ^^^^ can only appear in an import list with a non-empty prefix error: aborting due to previous error For more information about this error, try `rustc --explain E0431`. </ins>


<ins> // build-pass // compile-flags: -Zsave-analysis enum Enum2 { Variant8 { _field: bool }, } impl Enum2 { fn new_variant8() -> Enum2 { Self::Variant8 { _field: true } } } fn main() {} </ins>


use rustc_hir::lang_items::{ FutureTraitLangItem, PinTypeLangItem, SizedTraitLangItem, VaListTypeLangItem, }; <del> use rustc_hir::{ExprKind, GenericArg, HirIdMap, Item, ItemKind, Node, PatKind, QPath}; </del> <ins> use rustc_hir::{ExprKind, GenericArg, HirIdMap, ItemKind, Node, PatKind, QPath}; </ins> use rustc_index::bit_set::BitSet; use rustc_index::vec::Idx; use rustc_infer::infer;


"packed type cannot transitively contain a `#[repr(align)]` type" ); <del> let hir = tcx.hir(); let hir_id = hir.as_local_hir_id(def_spans[0].0.expect_local()); if let Node::Item(Item { ident, .. }) = hir.get(hir_id) { err.span_note( tcx.def_span(def_spans[0].0), &format!("`{}` has a `#[repr(align)]` attribute", ident), ); } </del> <ins> err.span_note( tcx.def_span(def_spans[0].0), &format!( "`{}` has a `#[repr(align)]` attribute", tcx.item_name(def_spans[0].0) ), ); </ins> if def_spans.len() > 2 { let mut first = true; for (adt_def, span) in def_spans.iter().skip(1).rev() { <del> let hir_id = hir.as_local_hir_id(adt_def.expect_local()); if let Node::Item(Item { ident, .. }) = hir.get(hir_id) { err.span_note( *span, &if first { format!( "`{}` contains a field of type `{}`", tcx.type_of(def.did), ident ) } else { format!("...which contains a field of type `{}`", ident) }, ); first = false; } </del> <ins> let ident = tcx.item_name(*adt_def); err.span_note( *span, &if first { format!( "`{}` contains a field of type `{}`", tcx.type_of(def.did), ident ) } else { format!("...which contains a field of type `{}`", ident) }, ); first = false; </ins> } }


<ins> #[repr(transparent)] pub struct PageTableEntry { entry: u64, } #[repr(align(4096))] #[repr(C)] pub struct PageTable { entries: [PageTableEntry; 512], } </ins>


<ins> // aux-build:issue-73112.rs extern crate issue_73112; fn main() { use issue_73112::PageTable; #[repr(C, packed)] struct SomeStruct { //~^ ERROR packed type cannot transitively contain a `#[repr(align)]` type [E0588] page_table: PageTable, } } </ins>


<ins> error[E0588]: packed type cannot transitively contain a `#[repr(align)]` type --> $DIR/issue-73112.rs:9:5 | LL | /     struct SomeStruct { LL | | LL | |         page_table: PageTable, LL | |     } | |_____^ | note: `PageTable` has a `#[repr(align)]` attribute --> $DIR/auxiliary/issue-73112.rs:8:1 | LL | / pub struct PageTable { LL | |     entries: [PageTableEntry; 512], LL | | } | |_^ error: aborting due to previous error For more information about this error, try `rustc --explain E0588`. </ins>


/// but shebang isn't a part of rust syntax. pub fn strip_shebang(input: &str) -> Option<usize> { // Shebang must start with `#!` literally, without any preceding whitespace. <del> if input.starts_with("#!") { let input_tail = &input[2..]; // Shebang must have something non-whitespace after `#!` on the first line. let first_line_tail = input_tail.lines().next()?; if first_line_tail.contains(|c| !is_whitespace(c)) { // Ok, this is a shebang but if the next non-whitespace token is `[` or maybe // a doc comment (due to `TokenKind::(Line,Block)Comment` ambiguity at lexer level), // then it may be valid Rust code, so consider it Rust code. let next_non_whitespace_token = tokenize(input_tail).map(|tok| tok.kind).find(|tok| !matches!(tok, TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment { .. }) ); if next_non_whitespace_token != Some(TokenKind::OpenBracket) { // No other choice than to consider this a shebang. return Some(2 + first_line_tail.len()); } </del> <ins> // For simplicity we consider any line starting with `#!` a shebang, // regardless of restrictions put on shebangs by specific platforms. if let Some(input_tail) = input.strip_prefix("#!") { // Ok, this is a shebang but if the next non-whitespace token is `[` or maybe // a doc comment (due to `TokenKind::(Line,Block)Comment` ambiguity at lexer level), // then it may be valid Rust code, so consider it Rust code. let next_non_whitespace_token = tokenize(input_tail).map(|tok| tok.kind).find(|tok| !matches!(tok, TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment { .. }) ); if next_non_whitespace_token != Some(TokenKind::OpenBracket) { // No other choice than to consider this a shebang. return Some(2 + input_tail.lines().next().unwrap_or_default().len()); </ins> } } None


<ins> #! // check-pass fn main() {} </ins>


<ins> #! // check-pass // ignore-tidy-end-whitespace fn main() {} </ins>


<del> #!/usr/bin/env rustx // run-pass pub fn main() { println!("Hello World"); } </del>


debug!("QueryNormalizer: result = {:#?}", result); debug!("QueryNormalizer: obligations = {:#?}", obligations); self.obligations.extend(obligations); <del> Ok(result.normalized_ty) </del> <ins> let res = result.normalized_ty; // `tcx.normalize_projection_ty` may normalize to a type that still has // unevaluated consts, so keep normalizing here if that's the case. if res != ty && res.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) { Ok(res.try_super_fold_with(self)?) } else { Ok(res) } </ins> } ty::Projection(data) => {


debug!("QueryNormalizer: result = {:#?}", result); debug!("QueryNormalizer: obligations = {:#?}", obligations); self.obligations.extend(obligations); <del> Ok(crate::traits::project::PlaceholderReplacer::replace_placeholders( </del> <ins> let res = crate::traits::project::PlaceholderReplacer::replace_placeholders( </ins> infcx, mapped_regions, mapped_types, mapped_consts, &self.universes, result.normalized_ty, <del> )) </del> <ins> ); // `tcx.normalize_projection_ty` may normalize to a type that still has // unevaluated consts, so keep normalizing here if that's the case. if res != ty && res.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) { Ok(res.try_super_fold_with(self)?) } else { Ok(res) } </ins> } _ => ty.try_super_fold_with(self), })()?; self.cache.insert(ty, res); Ok(res) }


<ins> // build-pass #![allow(incomplete_features)] #![feature(generic_const_exprs)] trait TraitOne { const MY_NUM: usize; type MyErr: std::fmt::Debug; fn do_one_stuff(arr: [u8; Self::MY_NUM]) -> Result<(), Self::MyErr>; } trait TraitTwo { fn do_two_stuff(); } impl<O: TraitOne> TraitTwo for O where [(); Self::MY_NUM]:, { fn do_two_stuff() { O::do_one_stuff([5; Self::MY_NUM]).unwrap() } } struct Blargotron; #[derive(Debug)] struct ErrTy<const N: usize>([(); N]); impl TraitOne for Blargotron { const MY_NUM: usize = 3; type MyErr = ErrTy<{ Self::MY_NUM }>; fn do_one_stuff(_arr: [u8; Self::MY_NUM]) -> Result<(), Self::MyErr> { Ok(()) } } fn main() { Blargotron::do_two_stuff(); } </ins>


<ins> // build-pass #![allow(incomplete_features)] #![feature(generic_const_exprs)] use std::convert::AsMut; use std::default::Default; trait Foo: Sized { type Baz: Default + AsMut<[u8]>; fn bar() { Self::Baz::default().as_mut(); } } impl Foo for () { type Baz = [u8; 1 * 1]; //type Baz = [u8; 1]; } fn main() { <() as Foo>::bar(); } </ins>


<ins> // build-pass #![allow(incomplete_features)] #![feature(generic_const_exprs)] trait Collate<Op> { type Pass; type Fail; fn collate(self) -> (Self::Pass, Self::Fail); } impl<Op> Collate<Op> for () { type Pass = (); type Fail = (); fn collate(self) -> ((), ()) { ((), ()) } } trait CollateStep<X, Prev> { type Pass; type Fail; fn collate_step(x: X, prev: Prev) -> (Self::Pass, Self::Fail); } impl<X, P, F> CollateStep<X, (P, F)> for () { type Pass = (X, P); type Fail = F; fn collate_step(x: X, (p, f): (P, F)) -> ((X, P), F) { ((x, p), f) } } struct CollateOpImpl<const MASK: u32>; trait CollateOpStep { type NextOp; type Apply; } impl<const MASK: u32> CollateOpStep for CollateOpImpl<MASK> where CollateOpImpl<{ MASK >> 1 }>: Sized, { type NextOp = CollateOpImpl<{ MASK >> 1 }>; type Apply = (); } impl<H, T, Op: CollateOpStep> Collate<Op> for (H, T) where T: Collate<Op::NextOp>, Op::Apply: CollateStep<H, (T::Pass, T::Fail)>, { type Pass = <Op::Apply as CollateStep<H, (T::Pass, T::Fail)>>::Pass; type Fail = <Op::Apply as CollateStep<H, (T::Pass, T::Fail)>>::Fail; fn collate(self) -> (Self::Pass, Self::Fail) { <Op::Apply as CollateStep<H, (T::Pass, T::Fail)>>::collate_step(self.0, self.1.collate()) } } fn collate<X, const MASK: u32>(x: X) -> (X::Pass, X::Fail) where X: Collate<CollateOpImpl<MASK>>, { x.collate() } fn main() { dbg!(collate::<_, 5>(("Hello", (42, ('!', ()))))); } </ins>


<ins> // build-pass #![allow(incomplete_features)] #![feature(generic_const_exprs)] pub trait Foo { fn foo(&self); } pub struct FooImpl<const N: usize>; impl<const N: usize> Foo for FooImpl<N> { fn foo(&self) {} } pub trait Bar: 'static { type Foo: Foo; fn get() -> &'static Self::Foo; } struct BarImpl; impl Bar for BarImpl { type Foo = FooImpl< { { 4 } }, >; fn get() -> &'static Self::Foo { &FooImpl } } pub fn boom<B: Bar>() { B::get().foo(); } fn main() { boom::<BarImpl>(); } </ins>


<ins> // build-pass #![feature(generic_const_exprs)] #![allow(incomplete_features)] trait Foo { type Output; fn foo() -> Self::Output; } impl Foo for [u8; 3] { type Output = [u8; 1 + 2]; fn foo() -> [u8; 3] { [1u8; 3] } } fn bug<const N: usize>() where [u8; N]: Foo, <[u8; N] as Foo>::Output: AsRef<[u8]>, { <[u8; N]>::foo().as_ref(); } fn main() { bug::<3>(); } </ins>


<ins> // build-pass #![allow(incomplete_features)] #![feature(generic_const_exprs)] use std::marker::PhantomData; fn main() { let x = FooImpl::<BarImpl<1>> { phantom: PhantomData }; let _ = x.foo::<BarImpl<1>>(); } trait Foo<T> where T: Bar, { fn foo<U>(&self) where T: Operation<U>, <T as Operation<U>>::Output: Bar; } struct FooImpl<T> where T: Bar, { phantom: PhantomData<T>, } impl<T> Foo<T> for FooImpl<T> where T: Bar, { fn foo<U>(&self) where T: Operation<U>, <T as Operation<U>>::Output: Bar, { <<T as Operation<U>>::Output as Bar>::error_occurs_here(); } } trait Bar { fn error_occurs_here(); } struct BarImpl<const N: usize>; impl<const N: usize> Bar for BarImpl<N> { fn error_occurs_here() {} } trait Operation<Rhs> { type Output; } //// Part-A: This causes error. impl<const M: usize, const N: usize> Operation<BarImpl<M>> for BarImpl<N> where BarImpl<{ N + M }>: Sized, { type Output = BarImpl<{ N + M }>; } //// Part-B: This doesn't cause error. // impl<const M: usize, const N: usize> Operation<BarImpl<M>> for BarImpl<N> { //     type Output = BarImpl<M>; // } //// Part-C: This also doesn't cause error. // impl<const M: usize, const N: usize> Operation<BarImpl<M>> for BarImpl<N> { //     type Output = BarImpl<{ M }>; // } </ins>


generics: &Generics, fields: &[@struct_field], visitor: ResolveVisitor) { <ins> let mut ident_map = HashMap::new::<ast::ident, @struct_field>(); for fields.iter().advance |&field| { match field.node.kind { named_field(ident, _) => { match ident_map.find(&ident) { Some(&prev_field) => { let ident_str = self.session.str_of(ident); self.session.span_err(field.span, fmt!("field `%s` is already declared", ident_str)); self.session.span_note(prev_field.span, "Previously declared here"); }, None => { ident_map.insert(ident, field); } } } _ => () } } </ins> // If applicable, create a rib for the type parameters. do self.with_type_parameter_rib(HasTypeParameters (generics, id, 0,


<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. struct BuildData { foo: int, foo: int, //~ ERROR field `foo` is already declared } fn main() { } </ins>


/// This can be used to safely get a strong reference (by calling [`upgrade`] /// later) or to deallocate the weak count by dropping the `Weak<T>`. /// <del> /// It takes ownership of one weak count (with the exception of pointers created by [`new`], /// as these don't have any corresponding weak count). </del> <ins> /// It takes ownership of one weak reference (with the exception of pointers created by [`new`], /// as these don't own anything; the method still works on them). </ins> /// /// # Safety /// <del> /// The pointer must have originated from the [`into_raw`]  and must still own its potential /// weak reference count. </del> <ins> /// The pointer must have originated from the [`into_raw`] and must still own its potential /// weak reference. </ins> /// <del> /// It is allowed for the strong count to be 0 at the time of calling this, but the weak count /// must be non-zero or the pointer must have originated from a dangling `Weak<T>` (one created /// by [`new`]). </del> <ins> /// It is allowed for the strong count to be 0 at the time of calling this. Nevertheless, this /// takes ownership of one weak reference currently represented as a raw pointer (the weak /// count is not modified by this operation) and therefore it must be paired with a previous /// call to [`into_raw`]. </ins> /// /// # Examples ///


/// Consumes the `Weak<T>` and turns it into a raw pointer. /// <del> /// This converts the weak pointer into a raw pointer, preserving the original weak count. It /// can be turned back into the `Weak<T>` with [`from_raw`]. </del> <ins> /// This converts the weak pointer into a raw pointer, while still preserving the ownership of /// one weak reference (the weak count is not modified by this operation). It can be turned /// back into the `Weak<T>` with [`from_raw`]. </ins> /// /// The same restrictions of accessing the target of the pointer as with /// [`as_ptr`] apply.


result } <del> /// Converts a raw pointer previously created by [`into_raw`] back into /// `Weak<T>`. </del> <ins> /// Converts a raw pointer previously created by [`into_raw`] back into `Weak<T>`. </ins> /// /// This can be used to safely get a strong reference (by calling [`upgrade`] /// later) or to deallocate the weak count by dropping the `Weak<T>`. /// <del> /// It takes ownership of one weak count (with the exception of pointers created by [`new`], /// as these don't have any corresponding weak count). </del> <ins> /// It takes ownership of one weak reference (with the exception of pointers created by [`new`], /// as these don't own anything; the method still works on them). </ins> /// /// # Safety /// /// The pointer must have originated from the [`into_raw`] and must still own its potential <del> /// weak reference count. /// /// It is allowed for the strong count to be 0 at the time of calling this, but the weak count /// must be non-zero or the pointer must have originated from a dangling `Weak<T>` (one created /// by [`new`]). </del> <ins> /// weak reference. </ins> /// <ins> /// It is allowed for the strong count to be 0 at the time of calling this. Nevertheless, this /// takes ownership of one weak reference currently represented as a raw pointer (the weak /// count is not modified by this operation) and therefore it must be paired with a previous /// call to [`into_raw`]. </ins> /// # Examples /// /// ```


Greater | // `{ 42 } > 3` GreaterEqual | // `{ 42 } >= 3` AssignOp(_) | // `{ 42 } +=` <del> LAnd | // `{ 42 } &&foo` </del> As | // `{ 42 } as usize` // Equal | // `{ 42 } == { 42 }`    Accepting these here would regress incorrect // NotEqual | // `{ 42 } != { 42 }  struct literals parser recovery.


// want to keep their span info to improve diagnostics in these cases in a later stage. (true, Some(AssocOp::Multiply)) | // `{ 42 } *foo = bar;` or `{ 42 } * 3` (true, Some(AssocOp::Subtract)) | // `{ 42 } -5` <del> (true, Some(AssocOp::LAnd)) | // `{ 42 } &&x` (#61475) </del> (true, Some(AssocOp::Add)) // `{ 42 } + 42 // If the next token is a keyword, then the tokens above *are* unambiguously incorrect: // `if x { a } else { b } && if y { c } else { d }` <del> if !self.look_ahead(1, |t| t.is_reserved_ident()) => { </del> <ins> if !self.look_ahead(1, |t| t.is_used_keyword()) => { // These cases are ambiguous and can't be identified in the parser alone. let sp = self.sess.source_map().start_point(self.token.span); self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span); false } (true, Some(AssocOp::LAnd)) => { // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }`. Separated from the // above due to #74233. </ins> // These cases are ambiguous and can't be identified in the parser alone. let sp = self.sess.source_map().start_point(self.token.span); self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);


} self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty); self.suggest_missing_await(err, expr, expected, expr_ty); <ins> self.suggest_missing_parentheses(err, expr); </ins> self.note_need_for_fn_pointer(err, expected, expr_ty); }


} } <ins> fn suggest_missing_parentheses(&self, err: &mut DiagnosticBuilder<'_>, expr: &hir::Expr<'_>) { let sp = self.tcx.sess.source_map().start_point(expr.span); if let Some(sp) = self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) { // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }` self.tcx.sess.parse_sess.expr_parentheses_needed(err, *sp, None); } } </ins> fn note_need_for_fn_pointer( &self, err: &mut DiagnosticBuilder<'_>,


<ins> // This is not autofixable because we give extra suggestions to end the first expression with `;`. fn foo(a: Option<u32>, b: Option<u32>) -> bool { if let Some(x) = a { true } else { false } //~^ ERROR mismatched types //~| ERROR mismatched types && //~ ERROR mismatched types if let Some(y) = a { true } else { false } } fn main() {} </ins>


<ins> error[E0308]: mismatched types --> $DIR/expr-as-stmt-2.rs:3:26 | LL |     if let Some(x) = a { true } else { false } |     ---------------------^^^^------------------ help: consider using a semicolon here |     |                    | |     |                    expected `()`, found `bool` |     expected this to be `()` error[E0308]: mismatched types --> $DIR/expr-as-stmt-2.rs:3:40 | LL |     if let Some(x) = a { true } else { false } |     -----------------------------------^^^^^--- help: consider using a semicolon here |     |                                  | |     |                                  expected `()`, found `bool` |     expected this to be `()` error[E0308]: mismatched types --> $DIR/expr-as-stmt-2.rs:6:5 | LL |   fn foo(a: Option<u32>, b: Option<u32>) -> bool { |                                             ---- expected `bool` because of return type LL |       if let Some(x) = a { true } else { false } |       ------------------------------------------ help: parentheses are required to parse this as an expression: `(if let Some(x) = a { true } else { false })` ... LL | /     && LL | |     if let Some(y) = a { true } else { false } | |______________________________________________^ expected `bool`, found `&&bool` error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0308`. </ins>


//~^ ERROR mismatched types } <del> fn qux(a: Option<u32>, b: Option<u32>) -> bool { (if let Some(x) = a { true } else { false }) && //~ ERROR expected expression if let Some(y) = a { true } else { false } } </del> fn moo(x: u32) -> bool { (match x { _ => 1,


//~^ ERROR mismatched types } <del> fn qux(a: Option<u32>, b: Option<u32>) -> bool { if let Some(x) = a { true } else { false } && //~ ERROR expected expression if let Some(y) = a { true } else { false } } </del> fn moo(x: u32) -> bool { match x { _ => 1,


|     | |     help: parentheses are required to parse this as an expression: `({ 42 })` <del> error: expected expression, found `&&` --> $DIR/expr-as-stmt.rs:30:5 | LL |     if let Some(x) = a { true } else { false } |     ------------------------------------------ help: parentheses are required to parse this as an expression: `(if let Some(x) = a { true } else { false })` LL |     && |     ^^ expected expression </del> error: expected expression, found `>` <del> --> $DIR/expr-as-stmt.rs:37:7 </del> <ins> --> $DIR/expr-as-stmt.rs:31:7 </ins> | LL |     } > 0 |       ^ expected expression


|     | |     help: parentheses are required to parse this as an expression: `({ 3 })` <del> error: aborting due to 10 previous errors </del> <ins> error: aborting due to 9 previous errors </ins> Some errors have detailed explanations: E0308, E0614. For more information about an error, try `rustc --explain E0308`.