From a32cff333dd34b7db886317470f1301f0266b9e7 Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Sun, 14 Jun 2020 14:08:28 +0200
Subject: [PATCH] Introduce paths crate

It's a good idea to distinguish between absolute and relative paths at
the type level, to avoid accidental dependency on the cwd, which
really shouldn't matter for rust-analyzer service
---
 Cargo.lock              |   4 ++
 crates/paths/Cargo.toml |   8 +++
 crates/paths/src/lib.rs | 123 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 135 insertions(+)
 create mode 100644 crates/paths/Cargo.toml
 create mode 100644 crates/paths/src/lib.rs

diff --git a/Cargo.lock b/Cargo.lock
index 308e36836fa..5848e61c7ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -834,6 +834,10 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "paths"
+version = "0.1.0"
+
 [[package]]
 name = "percent-encoding"
 version = "2.1.0"
diff --git a/crates/paths/Cargo.toml b/crates/paths/Cargo.toml
new file mode 100644
index 00000000000..646ee7fd54c
--- /dev/null
+++ b/crates/paths/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "paths"
+version = "0.1.0"
+authors = ["rust-analyzer developers"]
+edition = "2018"
+
+[lib]
+doctest = false
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs
new file mode 100644
index 00000000000..c7ce0c42f7f
--- /dev/null
+++ b/crates/paths/src/lib.rs
@@ -0,0 +1,123 @@
+//! Thin wrappers around `std::path`, distinguishing between absolute and
+//! relative paths.
+use std::{
+    convert::{TryFrom, TryInto},
+    ops,
+    path::{Component, Path, PathBuf},
+};
+
+#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
+pub struct AbsPathBuf(PathBuf);
+
+impl From<AbsPathBuf> for PathBuf {
+    fn from(AbsPathBuf(path_buf): AbsPathBuf) -> PathBuf {
+        path_buf
+    }
+}
+
+impl ops::Deref for AbsPathBuf {
+    type Target = AbsPath;
+    fn deref(&self) -> &AbsPath {
+        self.as_path()
+    }
+}
+
+impl AsRef<Path> for AbsPathBuf {
+    fn as_ref(&self) -> &Path {
+        self.0.as_path()
+    }
+}
+
+impl TryFrom<PathBuf> for AbsPathBuf {
+    type Error = PathBuf;
+    fn try_from(path_buf: PathBuf) -> Result<AbsPathBuf, PathBuf> {
+        if !path_buf.is_absolute() {
+            return Err(path_buf);
+        }
+        Ok(AbsPathBuf(path_buf))
+    }
+}
+
+impl TryFrom<&str> for AbsPathBuf {
+    type Error = PathBuf;
+    fn try_from(path: &str) -> Result<AbsPathBuf, PathBuf> {
+        AbsPathBuf::try_from(PathBuf::from(path))
+    }
+}
+
+impl AbsPathBuf {
+    pub fn as_path(&self) -> &AbsPath {
+        AbsPath::new_unchecked(self.0.as_path())
+    }
+    pub fn pop(&mut self) -> bool {
+        self.0.pop()
+    }
+}
+
+#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct AbsPath(Path);
+
+impl ops::Deref for AbsPath {
+    type Target = Path;
+    fn deref(&self) -> &Path {
+        &self.0
+    }
+}
+
+impl AsRef<Path> for AbsPath {
+    fn as_ref(&self) -> &Path {
+        &self.0
+    }
+}
+
+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);
+        }
+        Ok(AbsPath::new_unchecked(path))
+    }
+}
+
+impl AbsPath {
+    fn new_unchecked(path: &Path) -> &AbsPath {
+        unsafe { &*(path as *const Path as *const AbsPath) }
+    }
+
+    pub fn join(&self, path: impl AsRef<Path>) -> AbsPathBuf {
+        self.as_ref().join(path).try_into().unwrap()
+    }
+    pub fn normalize(&self) -> AbsPathBuf {
+        AbsPathBuf(normalize_path(&self.0))
+    }
+}
+
+// https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85
+fn normalize_path(path: &Path) -> PathBuf {
+    let mut components = path.components().peekable();
+    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
+        components.next();
+        PathBuf::from(c.as_os_str())
+    } else {
+        PathBuf::new()
+    };
+
+    for component in components {
+        match component {
+            Component::Prefix(..) => unreachable!(),
+            Component::RootDir => {
+                ret.push(component.as_os_str());
+            }
+            Component::CurDir => {}
+            Component::ParentDir => {
+                ret.pop();
+            }
+            Component::Normal(c) => {
+                ret.push(c);
+            }
+        }
+    }
+    ret
+}