diff --git a/Cargo.lock b/Cargo.lock index 703f0e5b8af9..a4c8911c12a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,7 +1123,7 @@ checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] name = "paths" -version = "0.0.0" +version = "0.1.0" [[package]] name = "percent-encoding" diff --git a/crates/flycheck/Cargo.toml b/crates/flycheck/Cargo.toml index d3d180ece512..42e86b8dd73a 100644 --- a/crates/flycheck/Cargo.toml +++ b/crates/flycheck/Cargo.toml @@ -19,4 +19,4 @@ jod-thread = "0.1.2" toolchain = { path = "../toolchain", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } +paths = { path = "../../lib/paths", version = "0.1.0" } diff --git a/crates/proc-macro-api/Cargo.toml b/crates/proc-macro-api/Cargo.toml index 85a1c13fe7d4..30e73211c555 100644 --- a/crates/proc-macro-api/Cargo.toml +++ b/crates/proc-macro-api/Cargo.toml @@ -23,7 +23,7 @@ tracing = "0.1.35" memmap2 = "0.5.4" snap = "1.0.5" -paths = { path = "../paths", version = "0.0.0" } +paths = { path = "../../lib/paths", version = "0.1.0" } tt = { path = "../tt", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" } profile = { path = "../profile", version = "0.0.0" } diff --git a/crates/proc-macro-srv/Cargo.toml b/crates/proc-macro-srv/Cargo.toml index 5746eac0b379..a3a8a4ec55db 100644 --- a/crates/proc-macro-srv/Cargo.toml +++ b/crates/proc-macro-srv/Cargo.toml @@ -22,7 +22,7 @@ memmap2 = "0.5.4" tt = { path = "../tt", version = "0.0.0" } mbe = { path = "../mbe", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } +paths = { path = "../../lib/paths", version = "0.1.0" } proc-macro-api = { path = "../proc-macro-api", version = "0.0.0" } crossbeam = "0.8.1" diff --git a/crates/proc-macro-srv/src/tests/mod.rs b/crates/proc-macro-srv/src/tests/mod.rs index 07222907f088..8941f3a88d32 100644 --- a/crates/proc-macro-srv/src/tests/mod.rs +++ b/crates/proc-macro-srv/src/tests/mod.rs @@ -160,7 +160,7 @@ fn list_test_macros() { #[test] fn test_version_check() { - let path = AbsPathBuf::assert(fixtures::proc_macro_test_dylib_path()); + let path = AbsPathBuf::try_from(fixtures::proc_macro_test_dylib_path()).unwrap(); let info = proc_macro_api::read_dylib_info(&path).unwrap(); assert!(info.version.1 >= 50); } diff --git a/crates/project-model/Cargo.toml b/crates/project-model/Cargo.toml index bc75d6faa383..8eb51c70827c 100644 --- a/crates/project-model/Cargo.toml +++ b/crates/project-model/Cargo.toml @@ -23,6 +23,6 @@ la-arena = { version = "0.3.0", path = "../../lib/la-arena" } cfg = { path = "../cfg", version = "0.0.0" } base-db = { path = "../base-db", version = "0.0.0" } toolchain = { path = "../toolchain", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } +paths = { path = "../../lib/paths", version = "0.1.0" } stdx = { path = "../stdx", version = "0.0.0" } profile = { path = "../profile", version = "0.0.0" } diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs index ee7f8339a76a..ad55fe365bca 100644 --- a/crates/project-model/src/build_scripts.rs +++ b/crates/project-model/src/build_scripts.rs @@ -147,7 +147,8 @@ impl WorkspaceBuildScripts { let out_dir = message.out_dir.into_os_string(); if !out_dir.is_empty() { let data = outputs[package].get_or_insert_with(Default::default); - data.out_dir = Some(AbsPathBuf::assert(PathBuf::from(out_dir))); + data.out_dir = + Some(AbsPathBuf::try_from(PathBuf::from(out_dir)).unwrap()); data.cfgs = cfgs; } if !message.env.is_empty() { @@ -168,7 +169,8 @@ impl WorkspaceBuildScripts { if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) { - let filename = AbsPathBuf::assert(PathBuf::from(&filename)); + let filename = + AbsPathBuf::try_from(PathBuf::from(&filename)).unwrap(); outputs[package] .get_or_insert_with(Default::default) .proc_macro_dylib_path = Some(filename); diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index 597880c2ca21..1591cbcac353 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -336,7 +336,10 @@ impl CargoWorkspace { id: id.repr.clone(), name: name.clone(), version: version.clone(), - manifest: AbsPathBuf::assert(PathBuf::from(&manifest_path)).try_into().unwrap(), + manifest: AbsPathBuf::try_from(PathBuf::from(&manifest_path)) + .unwrap() + .try_into() + .unwrap(), targets: Vec::new(), is_local, is_member, @@ -354,7 +357,7 @@ impl CargoWorkspace { let tgt = targets.alloc(TargetData { package: pkg, name: meta_tgt.name.clone(), - root: AbsPathBuf::assert(PathBuf::from(&meta_tgt.src_path)), + root: AbsPathBuf::try_from(PathBuf::from(&meta_tgt.src_path)).unwrap(), kind: TargetKind::new(meta_tgt.kind.as_slice()), is_proc_macro, required_features: meta_tgt.required_features.clone(), @@ -397,7 +400,7 @@ impl CargoWorkspace { } let workspace_root = - AbsPathBuf::assert(PathBuf::from(meta.workspace_root.into_os_string())); + AbsPathBuf::try_from(PathBuf::from(meta.workspace_root.into_os_string())).unwrap(); CargoWorkspace { packages, targets, workspace_root } } diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index e3f83084ac8a..c82b4f42a958 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -125,7 +125,7 @@ impl ProjectManifest { .filter_map(Result::ok) .map(|it| it.path().join("Cargo.toml")) .filter(|it| it.exists()) - .map(AbsPathBuf::assert) + .map(|x| AbsPathBuf::try_from(x).unwrap()) .filter_map(|it| it.try_into().ok()) .collect() } diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs index 362bb0f5e79c..86df86c41150 100644 --- a/crates/project-model/src/sysroot.rs +++ b/crates/project-model/src/sysroot.rs @@ -149,7 +149,7 @@ fn discover_sysroot_dir(current_dir: &AbsPath) -> Result { rustc.current_dir(current_dir).args(&["--print", "sysroot"]); tracing::debug!("Discovering sysroot by {:?}", rustc); let stdout = utf8_stdout(rustc)?; - Ok(AbsPathBuf::assert(PathBuf::from(stdout))) + Ok(AbsPathBuf::try_from(PathBuf::from(stdout)).unwrap()) } fn discover_sysroot_src_dir( @@ -157,8 +157,9 @@ fn discover_sysroot_src_dir( current_dir: &AbsPath, ) -> Result { if let Ok(path) = env::var("RUST_SRC_PATH") { - let path = AbsPathBuf::try_from(path.as_str()) - .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; + let path = AbsPathBuf::try_from(path.as_str()).map_err(|path| { + format_err!("RUST_SRC_PATH must be absolute: {}", path.into_inner().display()) + })?; let core = path.join("core"); if fs::metadata(&core).is_ok() { tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display()); diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index e304a59c0180..3945e9cbff94 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -77,7 +77,7 @@ fn get_fake_sysroot() -> Sysroot { let sysroot_path = get_test_path("fake-sysroot"); // there's no `libexec/` directory with a `proc-macro-srv` binary in that // fake sysroot, so we give them both the same path: - let sysroot_dir = AbsPathBuf::assert(sysroot_path); + let sysroot_dir = AbsPathBuf::try_from(sysroot_path).unwrap(); let sysroot_src_dir = sysroot_dir.clone(); Sysroot::load(sysroot_dir, sysroot_src_dir).unwrap() } @@ -86,7 +86,7 @@ fn rooted_project_json(data: ProjectJsonData) -> ProjectJson { let mut root = "$ROOT$".to_string(); replace_root(&mut root, true); let path = Path::new(&root); - let base = AbsPath::assert(path); + let base = <&AbsPath>::try_from(path).unwrap(); ProjectJson::new(base, data) } diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index e9de23cb395d..8fe54e2d93c6 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -168,7 +168,7 @@ fn run_server() -> Result<()> { Some(it) => it, None => { let cwd = env::current_dir()?; - AbsPathBuf::assert(cwd) + AbsPathBuf::try_from(cwd).unwrap() } }; diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index f52e1e751278..8ac974dc9e1d 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -65,7 +65,7 @@ impl flags::AnalysisStats { let mut db_load_sw = self.stop_watch(); - let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path)); + let path = AbsPathBuf::try_from(env::current_dir()?.join(&self.path)).unwrap(); let manifest = ProjectManifest::discover_single(&path)?; let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 5d1c013c3275..35876aaa4c30 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs @@ -29,7 +29,7 @@ pub fn load_workspace_at( load_config: &LoadCargoConfig, progress: &dyn Fn(String), ) -> Result<(AnalysisHost, vfs::Vfs, Option)> { - let root = AbsPathBuf::assert(std::env::current_dir()?.join(root)); + let root = AbsPathBuf::try_from(std::env::current_dir()?.join(root)).unwrap(); let root = ProjectManifest::discover_single(&root)?; let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?; @@ -59,7 +59,7 @@ pub fn load_workspace( }; let proc_macro_client = if load_config.with_proc_macro { - let path = AbsPathBuf::assert(std::env::current_exe()?); + let path = AbsPathBuf::try_from(std::env::current_exe()?).unwrap(); Ok(ProcMacroServer::spawn(path, &["proc-macro"]).unwrap()) } else { Err("proc macro server not started".to_owned()) diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index 491c55a04f8c..41e19ae72736 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -294,7 +294,7 @@ impl flags::Lsif { with_proc_macro: true, prefill_caches: false, }; - let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path)); + let path = AbsPathBuf::try_from(env::current_dir()?.join(&self.path)).unwrap(); let manifest = ProjectManifest::discover_single(&path)?; let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index ac0fdf85a774..d3a18c071c5a 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -609,7 +609,7 @@ impl Config { self.detached_files = get_field::>(&mut json, &mut errors, "detachedFiles", None, "[]") .into_iter() - .map(AbsPathBuf::assert) + .map(|x| AbsPathBuf::try_from(x).unwrap()) .collect(); patch_old_style::patch_json_for_outdated_configs(&mut json); self.data = ConfigData::from_json(json, &mut errors); @@ -902,7 +902,7 @@ impl Config { } let path = match &self.data.procMacro_server { Some(it) => self.root_path.join(it), - None => AbsPathBuf::assert(std::env::current_exe().ok()?), + None => AbsPathBuf::try_from(std::env::current_exe().ok()?).unwrap(), }; Some((path, vec!["proc-macro".into()])) } diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 47cdd8dfc75d..eeda00d4be14 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -47,7 +47,7 @@ fn integrated_highlighting_benchmark() { let file_id = { let file = workspace_to_load.join(file); - let path = VfsPath::from(AbsPathBuf::assert(file)); + let path = VfsPath::from(AbsPathBuf::try_from(file).unwrap()); vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {}", path)) }; @@ -101,7 +101,7 @@ fn integrated_completion_benchmark() { let file_id = { let file = workspace_to_load.join(file); - let path = VfsPath::from(AbsPathBuf::assert(file)); + let path = VfsPath::from(AbsPathBuf::try_from(file).unwrap()); vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {}", path)) }; diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index 4fa88c3c6da1..0642dec81960 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -89,7 +89,7 @@ impl<'a> Project<'a> { fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); } - let tmp_dir_path = AbsPathBuf::assert(tmp_dir.path().to_path_buf()); + let tmp_dir_path = AbsPathBuf::try_from(tmp_dir.path().to_path_buf()).unwrap(); let mut roots = self.roots.into_iter().map(|root| tmp_dir_path.join(root)).collect::>(); if roots.is_empty() { diff --git a/crates/vfs-notify/Cargo.toml b/crates/vfs-notify/Cargo.toml index 9ee4415dcada..212f90555047 100644 --- a/crates/vfs-notify/Cargo.toml +++ b/crates/vfs-notify/Cargo.toml @@ -17,4 +17,4 @@ crossbeam-channel = "0.5.5" notify = "=5.0.0-pre.15" vfs = { path = "../vfs", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } +paths = { path = "../../lib/paths", version = "0.1.0" } diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs index 4d33a9afb963..1e9e91652736 100644 --- a/crates/vfs-notify/src/lib.rs +++ b/crates/vfs-notify/src/lib.rs @@ -183,7 +183,7 @@ impl NotifyActor { if !entry.file_type().is_dir() { return true; } - let path = AbsPath::assert(entry.path()); + let path = <&AbsPath>::try_from(entry.path()).unwrap(); root == path || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path) }); @@ -191,7 +191,7 @@ impl NotifyActor { let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| { let is_dir = entry.file_type().is_dir(); let is_file = entry.file_type().is_file(); - let abs_path = AbsPathBuf::assert(entry.into_path()); + let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap(); if is_dir && watch { self.watch(abs_path.clone()); } diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml index c6377348784a..f92c64ccecd1 100644 --- a/crates/vfs/Cargo.toml +++ b/crates/vfs/Cargo.toml @@ -13,5 +13,5 @@ doctest = false rustc-hash = "1.1.0" fst = "0.4.7" -paths = { path = "../paths", version = "0.0.0" } +paths = { path = "../../lib/paths", version = "0.1.0" } indexmap = "1.9.1" diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index 668c7320d4ec..189d2f4d79fb 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs @@ -28,7 +28,7 @@ impl VfsPath { /// Create a path from string. Input should be a string representation of /// an absolute path inside filesystem pub fn new_real_path(path: String) -> VfsPath { - VfsPath::from(AbsPathBuf::assert(path.into())) + VfsPath::from(AbsPathBuf::try_from(path).unwrap()) } /// Returns the `AbsPath` representation of `self` if `self` is on the file system. diff --git a/lib/README.md b/lib/README.md index 6b2eeac2c0d7..8c790456fd54 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,2 +1,3 @@ Crates in this directory are published to crates.io and obey semver. -They *could* live in a separate repo, but we want to experiment with a monorepo setup. + +They _could_ live in a separate repo, but we want to experiment with a monorepo setup. diff --git a/crates/paths/Cargo.toml b/lib/paths/Cargo.toml similarity index 80% rename from crates/paths/Cargo.toml rename to lib/paths/Cargo.toml index 5e83de7d994e..2b3843392566 100644 --- a/crates/paths/Cargo.toml +++ b/lib/paths/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "paths" -version = "0.0.0" -description = "TBD" +version = "0.1.0" +description = "Wrapper types for relative and absolute paths." license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.57" diff --git a/crates/paths/src/lib.rs b/lib/paths/src/lib.rs similarity index 58% rename from crates/paths/src/lib.rs rename to lib/paths/src/lib.rs index 025093f4a94a..e501f63d4676 100644 --- a/crates/paths/src/lib.rs +++ b/lib/paths/src/lib.rs @@ -6,10 +6,15 @@ use std::{ borrow::Borrow, ffi::OsStr, - ops, + fmt, io, ops, path::{Component, Path, PathBuf}, }; +/// Returns an [`AbsPathBuf`] of the current directory joined with the `path`. +pub fn to_abs_path(path: &Path) -> io::Result { + Ok(std::env::current_dir()?.join(path).try_into().unwrap()) +} + /// Wrapper around an absolute [`PathBuf`]. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct AbsPathBuf(PathBuf); @@ -46,18 +51,45 @@ impl Borrow for AbsPathBuf { } impl TryFrom for AbsPathBuf { - type Error = PathBuf; - fn try_from(path_buf: PathBuf) -> Result { - if !path_buf.is_absolute() { - return Err(path_buf); + type Error = NotAbsPathBuf; + fn try_from(path_buf: PathBuf) -> Result { + if path_buf.is_absolute() { + Ok(AbsPathBuf(path_buf)) + } else { + Err(NotAbsPathBuf(path_buf)) } - Ok(AbsPathBuf(path_buf)) } } +/// The error returned by `impl TryFrom for AbsPathBuf` and others. +#[derive(Debug)] +pub struct NotAbsPathBuf(PathBuf); + +impl NotAbsPathBuf { + /// Returns the `PathBuf` that could not be converted. + pub fn into_inner(self) -> PathBuf { + self.0 + } +} + +impl fmt::Display for NotAbsPathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} is not an absolute path", self.0.display()) + } +} + +impl std::error::Error for NotAbsPathBuf {} + impl TryFrom<&str> for AbsPathBuf { - type Error = PathBuf; - fn try_from(path: &str) -> Result { + type Error = NotAbsPathBuf; + fn try_from(path: &str) -> Result { + AbsPathBuf::try_from(PathBuf::from(path)) + } +} + +impl TryFrom for AbsPathBuf { + type Error = NotAbsPathBuf; + fn try_from(path: String) -> Result { AbsPathBuf::try_from(PathBuf::from(path)) } } @@ -69,24 +101,15 @@ impl PartialEq for AbsPathBuf { } impl AbsPathBuf { - /// Wrap the given absolute path in `AbsPathBuf` + /// Coerces to an [`AbsPath`] slice. /// - /// # Panics - /// - /// Panics if `path` is not absolute. - pub fn assert(path: PathBuf) -> AbsPathBuf { - AbsPathBuf::try_from(path) - .unwrap_or_else(|path| panic!("expected absolute path, got {}", path.display())) - } - - /// Coerces to an `AbsPath` slice. - /// - /// Equivalent of [`PathBuf::as_path`] for `AbsPathBuf`. + /// Equivalent of [`PathBuf::as_path`]. pub fn as_path(&self) -> &AbsPath { - AbsPath::assert(self.0.as_path()) + // SAFETY: `AbsPathBuf` always contains an absolute path + unsafe { AbsPath::new_unchecked(self.0.as_path()) } } - /// Equivalent of [`PathBuf::pop`] for `AbsPathBuf`. + /// Equivalent of [`PathBuf::pop`]. /// /// Note that this won't remove the root component, so `self` will still be /// absolute. @@ -107,37 +130,54 @@ impl AsRef for AbsPath { } impl<'a> TryFrom<&'a Path> for &'a AbsPath { - type Error = &'a Path; - fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> { - if !path.is_absolute() { - return Err(path); + type Error = NotAbsPath<'a>; + fn try_from(path: &'a Path) -> Result<&'a AbsPath, NotAbsPath<'a>> { + if path.is_absolute() { + // SAFETY: just checked is absolute + Ok(unsafe { AbsPath::new_unchecked(path) }) + } else { + Err(NotAbsPath(path)) } - Ok(AbsPath::assert(path)) } } +/// The error returned by `impl TryFrom<&Path> for &AbsPath` and others. +#[derive(Debug)] +pub struct NotAbsPath<'a>(&'a Path); + +impl<'a> NotAbsPath<'a> { + /// Returns the `Path` that could not be converted. + pub fn into_inner(self) -> &'a Path { + self.0 + } +} + +impl<'a> fmt::Display for NotAbsPath<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} is not an absolute path", self.0.display()) + } +} + +impl<'a> std::error::Error for NotAbsPath<'a> {} + impl AbsPath { - /// Wrap the given absolute path in `AbsPath` - /// - /// # Panics - /// - /// Panics if `path` is not absolute. - pub fn assert(path: &Path) -> &AbsPath { - assert!(path.is_absolute()); - unsafe { &*(path as *const Path as *const AbsPath) } + unsafe fn new_unchecked(path: &Path) -> &AbsPath { + &*(path as *const Path as *const AbsPath) } /// Equivalent of [`Path::parent`] for `AbsPath`. pub fn parent(&self) -> Option<&AbsPath> { - self.0.parent().map(AbsPath::assert) + // SAFETY: the parent of an absolute path will be absolute + self.0.parent().map(|x| unsafe { AbsPath::new_unchecked(x) }) } /// Equivalent of [`Path::join`] for `AbsPath`. - pub fn join(&self, path: impl AsRef) -> AbsPathBuf { + pub fn join>(&self, path: P) -> AbsPathBuf { self.as_ref().join(path).try_into().unwrap() } /// Normalize the given path: + /// /// - Removes repeated separators: `/a//b` becomes `/a/b` /// - Removes occurrences of `.` and resolves `..`. /// - Removes trailing slashes: `/a/b/` becomes `/a/b`. @@ -150,7 +190,7 @@ impl AbsPath { /// assert_eq!(normalized, AbsPathBuf::assert("/b/c".into())); /// ``` pub fn normalize(&self) -> AbsPathBuf { - AbsPathBuf(normalize_path(&self.0)) + AbsPathBuf::try_from(normalize_path(&self.0)).unwrap() } /// Equivalent of [`Path::to_path_buf`] for `AbsPath`. @@ -162,11 +202,19 @@ impl AbsPath { /// /// Returns a relative path. pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> { - self.0.strip_prefix(base).ok().map(RelPath::new_unchecked) + self.0.strip_prefix(base).ok().map(|x| { + // SAFETY: if the prefix was stripped, the prefix must have been + // absolute + unsafe { RelPath::new_unchecked(x) } + }) } + + /// Returns whether `self` starts with `base`. pub fn starts_with(&self, base: &AbsPath) -> bool { self.0.starts_with(&base.0) } + + /// Returns whether `self` starts with `suffix`. pub fn ends_with(&self, suffix: &RelPath) -> bool { self.0.ends_with(&suffix.0) } @@ -181,25 +229,30 @@ impl AbsPath { // For `AbsPath`, we want to make sure that this is a POD type, and that all // IO goes via `fs`. That way, it becomes easier to mock IO when we need it. + /// Delegate for [`Path::file_name`]. pub fn file_name(&self) -> Option<&OsStr> { self.0.file_name() } + + /// Delegate for [`Path::extension`]. pub fn extension(&self) -> Option<&OsStr> { self.0.extension() } + + /// Delegate for [`Path::file_stem`]. pub fn file_stem(&self) -> Option<&OsStr> { self.0.file_stem() } + + /// Delegate for [`Path::as_os_str`]. pub fn as_os_str(&self) -> &OsStr { self.0.as_os_str() } + + /// Delegate for [`Path::display`]. pub fn display(&self) -> std::path::Display<'_> { self.0.display() } - #[deprecated(note = "use std::fs::metadata().is_ok() instead")] - pub fn exists(&self) -> bool { - self.0.exists() - } // endregion:delegate-methods } @@ -227,18 +280,38 @@ impl AsRef for RelPathBuf { } impl TryFrom for RelPathBuf { - type Error = PathBuf; - fn try_from(path_buf: PathBuf) -> Result { - if !path_buf.is_relative() { - return Err(path_buf); + type Error = NotRelPathBuf; + fn try_from(path_buf: PathBuf) -> Result { + if path_buf.is_relative() { + Ok(RelPathBuf(path_buf)) + } else { + Err(NotRelPathBuf(path_buf)) } - Ok(RelPathBuf(path_buf)) } } +/// The error returned by `impl TryFrom for RelPathBuf` and others. +#[derive(Debug)] +pub struct NotRelPathBuf(PathBuf); + +impl NotRelPathBuf { + /// Returns the `PathBuf` that could not be converted. + pub fn into_inner(self) -> PathBuf { + self.0 + } +} + +impl fmt::Display for NotRelPathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} is not an relative path", self.0.display()) + } +} + +impl std::error::Error for NotRelPathBuf {} + impl TryFrom<&str> for RelPathBuf { - type Error = PathBuf; - fn try_from(path: &str) -> Result { + type Error = NotRelPathBuf; + fn try_from(path: &str) -> Result { RelPathBuf::try_from(PathBuf::from(path)) } } @@ -248,7 +321,8 @@ impl RelPathBuf { /// /// Equivalent of [`PathBuf::as_path`] for `RelPathBuf`. pub fn as_path(&self) -> &RelPath { - RelPath::new_unchecked(self.0.as_path()) + // SAFETY: `RelPathBuf` always contains relative paths + unsafe { RelPath::new_unchecked(self.0.as_path()) } } } @@ -265,8 +339,8 @@ impl AsRef for RelPath { impl RelPath { /// Creates a new `RelPath` from `path`, without checking if it is relative. - pub fn new_unchecked(path: &Path) -> &RelPath { - unsafe { &*(path as *const Path as *const RelPath) } + unsafe fn new_unchecked(path: &Path) -> &RelPath { + &*(path as *const Path as *const RelPath) } }