compiler/crates/intern/src/path.rs (183 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ use crate::{idhasher::BuildIdHasher, intern_struct, string, InternId, InternSerdes}; use serde_derive::{Deserialize, Serialize}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use std::{ cmp::Ordering, ffi::OsStr, path::{Path, PathBuf}, }; intern_struct! { /// A path whose path components are interned. pub struct PathId = Intern<PathNode> { serdes("InternSerdes<PathId>"); type Set = PathIdSet; type Map = PathIdMap; } } pub type PathIdIndexMap<V> = indexmap::IndexMap<PathId, V, BuildIdHasher<u32>>; pub type PathIdIndexSet = indexmap::IndexSet<PathId, BuildIdHasher<u32>>; impl PathId { /// Intern the given path one `std::path::Component` at a time using the /// `Path::components()` iterator, which normalizes during parsing. Panic /// on empty paths such as `/`. #[cfg(unix)] pub fn intern<P: AsRef<Path>>(mut parent: Option<PathId>, path: P) -> Self { for c in path.as_ref().iter() { let p = InternId::intern(PathNode { name: string::intern_bytes(c.as_bytes()), parent, }); parent = Some(p); } parent.unwrap() } /// Intern the given path one `std::path::Component` at a time using the /// `Path::components()` iterator, which normalizes during parsing. Panic /// on empty paths such as `/`. #[cfg(not(unix))] pub fn intern<P: AsRef<Path>>(mut parent: Option<PathId>, path: P) -> Self { for c in path.as_ref().iter() { let p = InternId::intern(PathNode { name: string::intern(c.to_str().unwrap()), parent, }); parent = Some(p); } parent.unwrap() } pub fn parent(&self) -> Option<PathId> { self.parent } /// Returns the final component as an &OsStr. pub fn file_name(&self) -> &OsStr { self.get().file_name() } /// Linearize this path as a PathBuf. pub fn to_path_buf(&self) -> PathBuf { self.get().to_path_buf() } /// Linearize this path, appending to an existing PathBuf. pub fn push_to(&self, buf: &mut PathBuf) { self.get().push_to(buf); } } impl<P: AsRef<Path>> From<P> for PathId { fn from(path: P) -> Self { Self::intern(None, path) } } impl From<PathId> for PathBuf { fn from(id: PathId) -> Self { id.to_path_buf() } } impl std::fmt::Display for PathId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.to_path_buf().display().fmt(f) } } impl std::cmp::Ord for PathId { /// Compare the linearized components of self to other in top-down order. fn cmp(&self, other: &Self) -> Ordering { type LinearPath = smallvec::SmallVec<[PathComponentId; 20]>; fn linearize(mut p: PathId) -> LinearPath { let mut v: LinearPath = Default::default(); v.push(p.name); while let Some(parent) = p.parent { v.push(parent.name); p = parent; } v } linearize(*self) .iter() .rev() .cmp(linearize(*other).iter().rev()) } } impl std::cmp::PartialOrd for PathId { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) } } #[cfg(unix)] type PathComponentId = string::BytesId; #[cfg(not(unix))] type PathComponentId = string::StringId; /// A `PathNode` represents a `Path` as a list, starting with a leaf component /// and walking up the directory tree. This is really an internal detail, but /// is exposed by `PathId::Intern`. #[doc(hidden)] #[derive(Hash, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub struct PathNode { name: PathComponentId, parent: Option<PathId>, } impl PathNode { /// Returns the final component as an &OsStr. #[cfg(unix)] fn file_name(&self) -> &OsStr { OsStr::from_bytes(self.name.as_bytes()) } /// Returns the final component as an &OsStr. #[cfg(not(unix))] fn file_name(&self) -> &OsStr { OsStr::new(self.name.as_str()) } /// Linearize this path as a PathBuf. fn to_path_buf(&self) -> PathBuf { let mut path = match self.parent { Some(parent) => parent.to_path_buf(), None => PathBuf::new(), }; path.push(self.file_name()); path } /// Linearize this path, appending to an existing PathBuf. fn push_to(&self, buf: &mut PathBuf) { if let Some(parent) = self.parent { parent.push_to(buf); } buf.push(self.file_name()); } } impl std::fmt::Debug for PathNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.to_path_buf().fmt(f) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_eq() { assert_eq!(PathId::from("a"), PathId::from("a/")); assert_ne!(PathId::from("a"), PathId::from("/a")); assert_eq!(PathId::from("a/b"), PathId::from("a//b")); } #[test] fn test_cmp() { assert_eq!(PathId::from("a").cmp(&PathId::from("a")), Ordering::Equal); assert_eq!(PathId::from("a").cmp(&PathId::from("b")), Ordering::Less); assert_eq!(PathId::from("a").cmp(&PathId::from("a/a")), Ordering::Less); assert_eq!(PathId::from("a").cmp(&PathId::from("a/_")), Ordering::Less); assert_eq!(PathId::from("b").cmp(&PathId::from("a")), Ordering::Greater); assert_eq!( PathId::from("a/a").cmp(&PathId::from("a")), Ordering::Greater ); assert_eq!( PathId::from("a/_").cmp(&PathId::from("a")), Ordering::Greater ); assert_eq!( PathId::from("a/b").cmp(&PathId::from("a.b")), Ordering::Less ); } #[test] fn test_intern() { assert_eq!(PathId::from("a/b"), PathId::intern(None, "a/b")); assert_eq!( PathId::from("a/b/c"), PathId::intern(Some(PathId::from("a")), "b/c"), ); } }