Skip to content

Commit

Permalink
Restore FileSystem
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Jun 10, 2024
1 parent a0fcb82 commit fa3bb86
Show file tree
Hide file tree
Showing 8 changed files with 873 additions and 152 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/ruff_db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ countme = { workspace = true }
dashmap = { workspace = true }
filetime = { workspace = true }
salsa = { workspace = true }
tracing = { workspace = true }
rustc-hash = { workspace = true }
265 changes: 265 additions & 0 deletions crates/ruff_db/src/file_system.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
use std::fmt::Formatter;
use std::ops::Deref;
use std::path::Path;

use camino::{Utf8Path, Utf8PathBuf};
use filetime::FileTime;

pub use memory::MemoryFileSystem;
pub use os::OsFileSystem;

mod memory;
mod os;

pub type Result<T> = std::io::Result<T>;

/// A file system that can be used to read and write files.
///
/// The file system is agnostic to the actual storage medium, it could be a real file system, a combination
/// of a real file system and an in-memory file system in the case of an LSP where unsaved changes are stored in memory,
/// or an all in-memory file system for testing.
pub trait FileSystem {
/// Reads the metadata of the file or directory at `path`.
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata>;

/// Reads the content of the file at `path`.
fn read(&self, path: &FileSystemPath) -> Result<String>;

/// Returns `true` if `path` exists.
fn exists(&self, path: &FileSystemPath) -> bool;
}

// TODO support untitled files for the LSP use case. Wrap a `str` and `String`
// The main question is how `as_std_path` would work for untitled files, that can only exist in the LSP case
// but there's no compile time guarantee that a [`OsFileSystem`] never gets an untitled file path.

/// Path to a file or directory stored in [`FileSystem`].
///
/// The path is guaranteed to be valid UTF-8.
#[repr(transparent)]
#[derive(Eq, PartialEq, Hash)]
pub struct FileSystemPath(Utf8Path);

impl FileSystemPath {
pub fn new(path: &(impl AsRef<Utf8Path> + ?Sized)) -> &Self {
let path = path.as_ref();
unsafe { &*(path as *const Utf8Path as *const FileSystemPath) }
}

/// Converts the path to an owned [`FileSystemPathBuf`].
pub fn to_path_buf(&self) -> FileSystemPathBuf {
FileSystemPathBuf(self.0.to_path_buf())
}

/// Returns the path as a string slice.
pub fn as_str(&self) -> &str {
self.0.as_str()
}

/// Returns the std path for the file.
pub fn as_std_path(&self) -> &Path {
self.0.as_std_path()
}
}

/// Owned path to a file or directory stored in [`FileSystem`].
///
/// The path is guaranteed to be valid UTF-8.
#[repr(transparent)]
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct FileSystemPathBuf(Utf8PathBuf);

impl Default for FileSystemPathBuf {
fn default() -> Self {
Self::new()
}
}

impl FileSystemPathBuf {
pub fn new() -> Self {
Self(Utf8PathBuf::new())
}

pub fn as_path(&self) -> &FileSystemPath {
// SAFETY: FsPath is marked as #[repr(transparent)] so the conversion from a
// *const Utf8Path to a *const FsPath is valid.
unsafe { &*(self.0.as_path() as *const Utf8Path as *const FileSystemPath) }
}
}

impl AsRef<FileSystemPath> for FileSystemPathBuf {
fn as_ref(&self) -> &FileSystemPath {
self.as_path()
}
}

impl AsRef<FileSystemPath> for FileSystemPath {
#[inline]
fn as_ref(&self) -> &FileSystemPath {
self
}
}

impl AsRef<FileSystemPath> for str {
#[inline]
fn as_ref(&self) -> &FileSystemPath {
FileSystemPath::new(self)
}
}

impl AsRef<FileSystemPath> for String {
#[inline]
fn as_ref(&self) -> &FileSystemPath {
FileSystemPath::new(self)
}
}

impl AsRef<Path> for FileSystemPath {
#[inline]
fn as_ref(&self) -> &Path {
self.0.as_std_path()
}
}

impl Deref for FileSystemPathBuf {
type Target = FileSystemPath;

fn deref(&self) -> &Self::Target {
self.as_path()
}
}

impl std::fmt::Debug for FileSystemPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl std::fmt::Display for FileSystemPath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl std::fmt::Debug for FileSystemPathBuf {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl std::fmt::Display for FileSystemPathBuf {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Metadata {
revision: FileRevision,
permissions: Option<u32>,
file_type: FileType,
}

impl Metadata {
pub fn revision(&self) -> FileRevision {
self.revision
}

pub fn permissions(&self) -> Option<u32> {
self.permissions
}

pub fn file_type(&self) -> FileType {
self.file_type
}
}

/// A number representing the revision of a file.
///
/// Two revisions that don't compare equal signify that the file has been modified.
/// Revisions aren't guaranteed to be monotonically increasing or in any specific order.
///
/// Possible revisions are:
/// * The last modification time of the file.
/// * The hash of the file's content.
/// * The revision as it comes from an external system, for example the LSP.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct FileRevision(u128);

impl FileRevision {
pub fn new(value: u128) -> Self {
Self(value)
}

pub const fn zero() -> Self {
Self(0)
}

#[must_use]
pub fn as_u128(self) -> u128 {
self.0
}
}

impl From<u128> for FileRevision {
fn from(value: u128) -> Self {
FileRevision(value)
}
}

impl From<u64> for FileRevision {
fn from(value: u64) -> Self {
FileRevision(u128::from(value))
}
}

impl From<FileTime> for FileRevision {
fn from(value: FileTime) -> Self {
let seconds = value.seconds() as u128;
let seconds = seconds << 64;
let nanos = value.nanoseconds() as u128;

FileRevision(seconds | nanos)
}
}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub enum FileType {
File,
Directory,
Symlink,
}

impl FileType {
pub const fn is_file(self) -> bool {
matches!(self, FileType::File)
}

pub const fn is_directory(self) -> bool {
matches!(self, FileType::Directory)
}

pub const fn is_symlink(self) -> bool {
matches!(self, FileType::Symlink)
}
}

#[cfg(test)]
mod tests {
use crate::file_system::FileRevision;
use filetime::FileTime;

#[test]
fn revision_from_file_time() {
let file_time = FileTime::now();
let revision = FileRevision::from(file_time);

let revision = revision.as_u128();

let nano = revision & 0xFFFF_FFFF_FFFF_FFFF;
let seconds = revision >> 64;

assert_eq!(file_time.nanoseconds(), nano as u32);
assert_eq!(file_time.seconds(), seconds as i64);
}
}
Loading

0 comments on commit fa3bb86

Please sign in to comment.