1use std::fmt::{self, Debug, Formatter};
4use std::num::NonZeroU16;
5use std::sync::{LazyLock, RwLock};
6
7use rustc_hash::FxHashMap;
8
9use crate::VirtualPath;
10use crate::package::PackageSpec;
11
12static INTERNER: LazyLock<RwLock<Interner>> = LazyLock::new(|| {
14 RwLock::new(Interner { to_id: FxHashMap::default(), from_id: Vec::new() })
15});
16
17struct Interner {
19 to_id: FxHashMap<Pair, FileId>,
20 from_id: Vec<Pair>,
21}
22
23type Pair = &'static (Option<PackageSpec>, VirtualPath);
25
26#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
30pub struct FileId(NonZeroU16);
31
32impl FileId {
33 #[track_caller]
38 pub fn new(package: Option<PackageSpec>, path: VirtualPath) -> Self {
39 let pair = (package, path);
45 let mut interner = INTERNER.write().unwrap();
46 if let Some(&id) = interner.to_id.get(&pair) {
47 return id;
48 }
49
50 let num = u16::try_from(interner.from_id.len() + 1)
54 .and_then(NonZeroU16::try_from)
55 .expect("out of file ids");
56
57 let id = FileId(num);
58 let leaked = Box::leak(Box::new(pair));
59 interner.to_id.insert(leaked, id);
60 interner.from_id.push(leaked);
61 id
62 }
63
64 #[track_caller]
72 pub fn new_fake(path: VirtualPath) -> Self {
73 let mut interner = INTERNER.write().unwrap();
74 let num = u16::try_from(interner.from_id.len() + 1)
75 .and_then(NonZeroU16::try_from)
76 .expect("out of file ids");
77
78 let id = FileId(num);
79 let leaked = Box::leak(Box::new((None, path)));
80 interner.from_id.push(leaked);
81 id
82 }
83
84 pub fn package(&self) -> Option<&'static PackageSpec> {
86 self.pair().0.as_ref()
87 }
88
89 pub fn vpath(&self) -> &'static VirtualPath {
92 &self.pair().1
93 }
94
95 pub fn join(self, path: &str) -> Self {
97 Self::new(self.package().cloned(), self.vpath().join(path))
98 }
99
100 pub fn with_extension(&self, extension: &str) -> Self {
102 Self::new(self.package().cloned(), self.vpath().with_extension(extension))
103 }
104
105 pub const fn from_raw(v: NonZeroU16) -> Self {
111 Self(v)
112 }
113
114 pub const fn into_raw(self) -> NonZeroU16 {
116 self.0
117 }
118
119 fn pair(&self) -> Pair {
121 INTERNER.read().unwrap().from_id[usize::from(self.0.get() - 1)]
122 }
123}
124
125impl Debug for FileId {
126 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
127 let vpath = self.vpath();
128 match self.package() {
129 Some(package) => write!(f, "{package:?}{vpath:?}"),
130 None => write!(f, "{vpath:?}"),
131 }
132 }
133}