1use core::{
2 fmt::Write,
3 hash::{Hash, Hasher},
4 str::FromStr,
5};
6
7use anyhow::bail;
8use rustc_hash::{FxHashMap, FxHashSet};
9
10use crate::{
11 diagnostics::{SourceSpan, Spanned},
12 FunctionIdent, Ident, Symbol,
13};
14
15#[derive(Default, Debug, Clone)]
16pub struct ModuleImportInfo {
17 modules: FxHashMap<Ident, MasmImport>,
19 aliases: FxHashMap<Ident, Ident>,
21 functions: FxHashMap<Ident, FxHashSet<FunctionIdent>>,
23}
24impl ModuleImportInfo {
25 pub fn insert(&mut self, import: MasmImport) {
27 let name = Ident::new(import.name, import.span);
28 assert!(self.modules.insert(name, import).is_none());
29 }
30
31 pub fn add(&mut self, id: FunctionIdent) {
35 use std::collections::hash_map::Entry;
36
37 let module_id = id.module;
38 match self.modules.entry(module_id) {
39 Entry::Vacant(entry) => {
40 let alias = module_id_alias(module_id);
41 let span = module_id.span();
42 let alias_id = if self.aliases.contains_key(&alias) {
43 let mut hasher = rustc_hash::FxHasher::default();
48 alias.as_str().hash(&mut hasher);
49 let mut buf = String::with_capacity(16);
50 write!(&mut buf, "{:x}", hasher.finish()).expect("failed to write string");
51 let alias = Symbol::intern(buf.as_str());
52 let alias_id = Ident::new(alias, span);
53 assert_eq!(
54 self.aliases.insert(alias_id, module_id),
55 None,
56 "unexpected aliasing conflict"
57 );
58 alias_id
59 } else {
60 Ident::new(alias, span)
61 };
62 entry.insert(MasmImport {
63 span,
64 name: module_id.as_symbol(),
65 alias: alias_id.name,
66 });
67 self.aliases.insert(alias_id, module_id);
68 self.functions.entry(alias_id).or_default().insert(id);
69 }
70 Entry::Occupied(_) => {
71 let module_id_alias = module_id_alias(module_id);
72 let alias = self.aliases[&module_id_alias];
73 let functions = self.functions.entry(alias).or_default();
74 functions.insert(id);
75 }
76 }
77 }
78
79 pub fn is_empty(&self) -> bool {
81 self.modules.is_empty()
82 }
83
84 pub fn get<Q>(&self, module: &Q) -> Option<&MasmImport>
86 where
87 Ident: core::borrow::Borrow<Q>,
88 Q: Hash + Eq + ?Sized,
89 {
90 self.modules.get(module)
91 }
92
93 pub fn alias<Q>(&self, module: &Q) -> Option<Ident>
95 where
96 Ident: core::borrow::Borrow<Q>,
97 Q: Hash + Eq + ?Sized,
98 {
99 self.modules.get(module).map(|i| Ident::new(i.alias, i.span))
100 }
101
102 pub fn unalias<Q>(&self, alias: &Q) -> Option<Ident>
104 where
105 Ident: core::borrow::Borrow<Q>,
106 Q: Hash + Eq + ?Sized,
107 {
108 self.aliases.get(alias).copied()
109 }
110
111 pub fn is_import<Q>(&self, module: &Q) -> bool
113 where
114 Ident: core::borrow::Borrow<Q>,
115 Q: Hash + Eq + ?Sized,
116 {
117 self.modules.contains_key(module)
118 }
119
120 pub fn imported<Q>(&self, alias: &Q) -> Option<&FxHashSet<FunctionIdent>>
122 where
123 Ident: core::borrow::Borrow<Q>,
124 Q: Hash + Eq + ?Sized,
125 {
126 self.functions.get(alias)
127 }
128
129 pub fn iter(&self) -> impl Iterator<Item = &MasmImport> {
131 self.modules.values()
132 }
133
134 pub fn iter_module_names(&self) -> impl Iterator<Item = &Ident> {
136 self.modules.keys()
137 }
138}
139
140fn module_id_alias(module_id: Ident) -> Symbol {
141 match module_id.as_str().rsplit_once("::") {
142 None => module_id.as_symbol(),
143 Some((_, alias)) => Symbol::intern(alias),
144 }
145}
146
147#[derive(Debug, Copy, Clone, Spanned)]
149pub struct MasmImport {
150 #[span]
152 pub span: SourceSpan,
153 pub name: Symbol,
155 pub alias: Symbol,
162}
163impl MasmImport {
164 pub fn is_aliased(&self) -> bool {
167 !self.name.as_str().ends_with(self.alias.as_str())
168 }
169
170 pub fn conflicts_with(&self, other: &Self) -> bool {
175 self.alias == other.alias && self.name != other.name
176 }
177}
178impl Eq for MasmImport {}
179impl PartialEq for MasmImport {
180 fn eq(&self, other: &Self) -> bool {
181 if self.name != other.name {
183 return false;
184 }
185 match (self.is_aliased(), other.is_aliased()) {
187 (true, true) => {
188 self.alias == other.alias
191 }
192 (true, false) | (false, true) => {
193 false
197 }
198 (false, false) => {
199 true
201 }
202 }
203 }
204}
205impl PartialOrd for MasmImport {
206 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
207 Some(self.cmp(other))
208 }
209}
210impl Ord for MasmImport {
211 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
212 self.name.cmp(&other.name).then_with(|| self.alias.cmp(&other.alias))
213 }
214}
215impl Hash for MasmImport {
216 fn hash<H: Hasher>(&self, state: &mut H) {
217 self.name.hash(state);
218 self.alias.hash(state);
219 }
220}
221impl TryFrom<Ident> for MasmImport {
222 type Error = anyhow::Error;
223
224 fn try_from(module: Ident) -> Result<Self, Self::Error> {
225 let name = module.as_str();
226 if name.contains(char::is_whitespace) {
227 bail!("invalid module identifier '{name}': cannot contain whitespace",);
228 }
229 match name.rsplit_once("::") {
230 None => {
231 let name = module.as_symbol();
232 Ok(Self {
233 span: module.span(),
234 name,
235 alias: name,
236 })
237 }
238 Some((_, "")) => {
239 bail!("invalid module identifier '{name}': trailing '::' is invalid");
240 }
241 Some((_, alias)) => {
242 let name = module.as_symbol();
243 let alias = Symbol::intern(alias);
244 Ok(Self {
245 span: module.span(),
246 name,
247 alias,
248 })
249 }
250 }
251 }
252}
253impl FromStr for MasmImport {
254 type Err = anyhow::Error;
255
256 fn from_str(s: &str) -> Result<Self, Self::Err> {
258 let s = s.trim();
259 let s = s.strip_prefix("use ").unwrap_or(s);
260 match s.rsplit_once("->") {
261 None => {
262 let name = Ident::with_empty_span(Symbol::intern(s));
263 name.try_into()
264 }
265 Some((_, "")) => {
266 bail!("invalid import '{s}': alias cannot be empty")
267 }
268 Some((fqn, alias)) => {
269 let name = Symbol::intern(fqn);
270 let alias = Symbol::intern(alias);
271 Ok(Self {
272 span: SourceSpan::UNKNOWN,
273 name,
274 alias,
275 })
276 }
277 }
278 }
279}