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
15pub fn reset_interner() {
22 *INTERNER.write().unwrap() = FileInterner {
23 from_id: Vec::new(),
24 to_id: HashMap::new(),
25 }
26}
27
28struct FileInterner {
29 from_id: Vec<FileRef>,
30 to_id: HashMap<FileRef, FileId>,
31}
32
33type FileRef = &'static VirtualPath;
34
35#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
37pub struct VirtualPath(pub PathBuf);
38
39impl<T> From<T> for VirtualPath
40where
41 T: Into<PathBuf>,
42{
43 fn from(value: T) -> Self {
44 VirtualPath::new(value)
45 }
46}
47
48impl VirtualPath {
49 pub fn new(path: impl Into<PathBuf>) -> Self {
51 let path = path.into();
52 Self(path.clone().canonicalize().unwrap_or(path))
53 }
54
55 pub fn display(&self) -> String {
56 self.0.display().to_string()
57 }
58}
59
60impl Debug for VirtualPath {
61 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
62 Display::fmt(&self.0.display(), f)
63 }
64}
65
66#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
70pub struct FileId(NonZeroU16);
71
72impl FileId {
73 #[track_caller]
74 pub fn new(path: impl Into<VirtualPath>) -> Self {
75 let path = path.into();
77 let interner = INTERNER.read().unwrap();
78 if let Some(&id) = interner.to_id.get(&path) {
79 return id;
80 }
81
82 let num = u16::try_from(interner.from_id.len() + 1)
83 .and_then(NonZeroU16::try_from)
84 .expect("out of file ids");
85 drop(interner);
86
87 let id = FileId(num);
88 let leaked = Box::leak(Box::new(path));
89
90 let mut interner = INTERNER.write().unwrap();
91 interner.to_id.insert(leaked, id);
92 interner.from_id.push(leaked);
93 id
94 }
95
96 #[track_caller]
100 pub fn fake(path: impl Into<VirtualPath>) -> Self {
101 let mut interner = INTERNER.write().unwrap();
102 let num = u16::try_from(interner.from_id.len() + 1)
103 .and_then(NonZeroU16::try_from)
104 .expect("out of file ids");
105
106 let id = FileId(num);
107 let leaked = Box::leak(Box::new(path.into()));
108 interner.from_id.push(leaked);
109 id
110 }
111
112 #[track_caller]
113 pub fn path(&self) -> &'static VirtualPath {
114 self.try_path().expect("file id not interned")
115 }
116
117 pub fn try_path(&self) -> Option<&'static VirtualPath> {
118 let interner = INTERNER.try_read().ok()?;
119 let id = usize::from(self.0.get() - 1);
120
121 interner.from_id.get(id).copied()
122 }
123
124 pub(crate) const fn into_raw(self) -> NonZeroU16 {
126 self.0
127 }
128 pub(crate) const fn from_raw(raw: NonZeroU16) -> Self {
130 Self(raw)
131 }
132}
133
134impl Debug for FileId {
135 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
136 let path = self
137 .try_path()
138 .map(|p| format!("{:?}", p))
139 .unwrap_or_else(|| String::from("not interned"));
140 write!(f, "FileId({id}, {path})", id = self.0.get())
141 }
142}