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