compose_syntax/
file.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::fmt::{Debug, Display, Formatter};
4use std::num::NonZeroU16;
5use std::path::PathBuf;
6use std::sync::{LazyLock, RwLock};
7
8static INTERNER: LazyLock<RwLock<FileInterner>> = LazyLock::new(|| {
9    RwLock::new(FileInterner {
10        from_id: Vec::new(),
11        to_id: HashMap::new(),
12    })
13});
14
15struct FileInterner {
16    from_id: Vec<FileRef>,
17    to_id: HashMap<FileRef, FileId>,
18}
19
20type FileRef = &'static VirtualPath;
21
22/// An absolute path in the virtual file system of a project
23#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
24pub struct VirtualPath(pub PathBuf);
25
26impl<T> From<T> for VirtualPath
27where
28    T: Into<PathBuf>,
29{
30    fn from(value: T) -> Self {
31        VirtualPath::new(value)   
32    }
33}
34
35impl VirtualPath {
36    /// Create a new virtual path.
37    pub fn new(path: impl Into<PathBuf>) -> Self {
38        let path = path.into();
39        Self(path)
40    }
41
42    pub fn display(&self) -> String {
43        self.0.display().to_string()
44    }
45    
46    pub fn as_path(&self) -> &PathBuf {
47        &self.0
48    }
49}
50
51impl Debug for VirtualPath {
52    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
53        Display::fmt(&self.0.display(), f)
54    }
55}
56
57/// Identifier for a file,
58///
59/// Globally interned and thus cheap to copy, compare, and hash.
60#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
61pub struct FileId(NonZeroU16);
62
63impl FileId {
64    #[track_caller]
65    pub fn new(path: impl Into<VirtualPath>) -> Self {
66        // Check if the file is already in the interner.
67        let path = path.into();
68        let interner = INTERNER.read().unwrap();
69        if let Some(&id) = interner.to_id.get(&path) {
70            return id;
71        }
72
73        let num = u16::try_from(interner.from_id.len() + 1)
74            .and_then(NonZeroU16::try_from)
75            .expect("out of file ids");
76        drop(interner);
77
78        let id = FileId(num);
79        let leaked = Box::leak(Box::new(path));
80        
81        let mut interner = INTERNER.write().unwrap();
82        interner.to_id.insert(leaked, id);
83        interner.from_id.push(leaked);
84        id
85    }
86
87    /// Create a fake file id for a virtual path.
88    ///
89    /// Always returns a new file id, no matter if the file is already in the interner.
90    #[track_caller]
91    pub fn fake(path: impl Into<VirtualPath>) -> Self {
92        let mut interner = INTERNER.write().unwrap();
93        let num = u16::try_from(interner.from_id.len() + 1)
94            .and_then(NonZeroU16::try_from)
95            .expect("out of file ids");
96
97        let id = FileId(num);
98        let leaked = Box::leak(Box::new(path.into()));
99        interner.from_id.push(leaked);
100        id
101    }
102
103    #[track_caller]
104    pub fn path(&self) -> &'static VirtualPath {
105        self.try_path().expect("file id not interned")
106    }
107
108    pub fn try_path(&self) -> Option<&'static VirtualPath> {
109        let interner = INTERNER.try_read().ok()?;
110        let id = usize::from(self.0.get() - 1);
111
112        interner.from_id.get(id).copied()
113    }
114
115    /// Extract the raw underlying number
116    pub(crate) const fn into_raw(self) -> NonZeroU16 {
117        self.0
118    }
119    /// Create a FileId from a raw NonZeroU16
120    pub(crate) const fn from_raw(raw: NonZeroU16) -> Self {
121        Self(raw)
122    }
123}
124
125impl Debug for FileId {
126    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
127        let path = self
128            .try_path()
129            .map(|p| format!("{:?}", p))
130            .unwrap_or_else(|| String::from("not interned"));
131        write!(f, "FileId({id}, {path})", id = self.0.get())
132    }
133}