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#[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 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#[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 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 #[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 pub(crate) const fn into_raw(self) -> NonZeroU16 {
117 self.0
118 }
119 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}