1use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
2
3use miden_debug_types::{SourceManager, Span, Spanned};
4
5use super::{SymbolResolution, SymbolResolutionError};
6use crate::{
7 Path,
8 ast::{AliasTarget, Ident, ItemIndex},
9};
10
11const MAX_ALIAS_EXPANSION_DEPTH: usize = 128;
16
17pub trait SymbolTable {
19 type SymbolIter: Iterator<Item = LocalSymbol>;
21
22 fn symbols(&self, source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter;
25}
26
27impl SymbolTable for &crate::library::ModuleInfo {
28 type SymbolIter = alloc::vec::IntoIter<LocalSymbol>;
29
30 fn symbols(&self, _source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter {
31 let module_items = self.items();
32 let mut items = Vec::with_capacity(module_items.len());
33
34 for (index, item) in module_items {
35 let name = item.name().clone();
36 let span = name.span();
37
38 assert_eq!(index.as_usize(), items.len());
39 items.push(LocalSymbol::Item {
40 name,
41 resolved: SymbolResolution::Local(Span::new(span, index)),
42 });
43 }
44
45 items.into_iter()
46 }
47}
48
49impl SymbolTable for &crate::ast::Module {
50 type SymbolIter = alloc::vec::IntoIter<LocalSymbol>;
51
52 fn symbols(&self, source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter {
53 use crate::ast::{AliasTarget, Export};
54
55 let mut items = Vec::with_capacity(self.items.len());
56
57 for (i, item) in self.items.iter().enumerate() {
58 let id = ItemIndex::new(i);
59 let name = item.name().clone();
60 let span = name.span();
61 let name = name.into_inner();
62
63 if let Export::Alias(alias) = item {
64 match alias.target() {
65 AliasTarget::MastRoot(root) => {
66 items.push(LocalSymbol::Import {
67 name: Span::new(span, name),
68 resolution: Ok(SymbolResolution::MastRoot(*root)),
69 });
70 },
71 AliasTarget::Path(path) => {
72 let expanded = LocalSymbolTable::expand(
73 |name| self.get_import(name).map(|alias| alias.target().clone()),
74 path.as_deref(),
75 &source_manager,
76 );
77 items.push(LocalSymbol::Import {
78 name: Span::new(span, name),
79 resolution: expanded,
80 });
81 },
82 }
83 } else {
84 items.push(LocalSymbol::Item {
85 name: Ident::from_raw_parts(Span::new(span, name)),
86 resolved: SymbolResolution::Local(Span::new(span, id)),
87 });
88 }
89 }
90
91 items.into_iter()
92 }
93}
94
95#[derive(Debug)]
97pub enum LocalSymbol {
98 Item { name: Ident, resolved: SymbolResolution },
100 Import {
102 name: Span<Arc<str>>,
103 resolution: Result<SymbolResolution, SymbolResolutionError>,
104 },
105}
106
107impl LocalSymbol {
108 pub fn name(&self) -> &str {
109 match self {
110 Self::Item { name, .. } => name.as_str(),
111 Self::Import { name, .. } => name,
112 }
113 }
114}
115
116pub(super) struct LocalSymbolTable {
118 source_manager: Arc<dyn SourceManager>,
119 symbols: BTreeMap<Arc<str>, ItemIndex>,
120 items: Vec<LocalSymbol>,
121}
122
123impl core::ops::Index<ItemIndex> for LocalSymbolTable {
124 type Output = LocalSymbol;
125
126 #[inline(always)]
127 fn index(&self, index: ItemIndex) -> &Self::Output {
128 &self.items[index.as_usize()]
129 }
130}
131
132impl LocalSymbolTable {
133 pub fn new<S>(iter: S, source_manager: Arc<dyn SourceManager>) -> Self
134 where
135 S: SymbolTable,
136 {
137 let mut symbols = BTreeMap::default();
138 let mut items = Vec::with_capacity(16);
139
140 for (i, symbol) in iter.symbols(source_manager.clone()).enumerate() {
141 log::debug!(target: "symbol-table::new", "registering {} symbol: {}", match symbol {
142 LocalSymbol::Item { .. } => "local",
143 LocalSymbol::Import { .. } => "imported",
144 }, symbol.name());
145
146 let id = ItemIndex::new(i);
147 let name = match &symbol {
148 LocalSymbol::Item { name, .. } => name.clone().into_inner(),
149 LocalSymbol::Import { name, .. } => name.clone().into_inner(),
150 };
151
152 symbols.insert(name, id);
153 items.push(symbol);
154 }
155
156 Self { source_manager, symbols, items }
157 }
158
159 #[inline(always)]
160 pub fn source_manager(&self) -> &dyn SourceManager {
161 &self.source_manager
162 }
163
164 #[inline(always)]
165 pub fn source_manager_arc(&self) -> Arc<dyn SourceManager> {
166 self.source_manager.clone()
167 }
168}
169
170impl LocalSymbolTable {
171 pub fn get(&self, name: Span<&str>) -> Result<SymbolResolution, SymbolResolutionError> {
181 log::debug!(target: "symbol-table", "attempting to resolve '{name}'");
182 let (span, name) = name.into_parts();
183 let Some(item) = self.symbols.get(name).copied() else {
184 return Err(SymbolResolutionError::undefined(span, &self.source_manager));
185 };
186 match &self.items[item.as_usize()] {
187 LocalSymbol::Item { resolved, .. } => {
188 log::debug!(target: "symbol-table", "resolved '{name}' to {resolved:?}");
189 Ok(resolved.clone())
190 },
191 LocalSymbol::Import { name, resolution } => {
192 log::debug!(target: "symbol-table", "'{name}' refers to an import");
193 match resolution {
194 Ok(resolved) => {
195 log::debug!(target: "symbol-table", "resolved '{name}' to {resolved:?}");
196 Ok(resolved.clone())
197 },
198 Err(err) => {
199 log::error!(target: "symbol-table", "resolution of '{name}' failed: {err}");
200 Err(err.clone())
201 },
202 }
203 },
204 }
205 }
206
207 pub fn expand<F>(
241 get_import: F,
242 path: Span<&Path>,
243 source_manager: &dyn SourceManager,
244 ) -> Result<SymbolResolution, SymbolResolutionError>
245 where
246 F: Fn(&str) -> Option<AliasTarget>,
247 {
248 let mut expansion_stack = Vec::new();
249 Self::expand_with_guard(get_import, path, source_manager, &mut expansion_stack)
250 }
251
252 fn expand_with_guard<F>(
253 get_import: F,
254 path: Span<&Path>,
255 source_manager: &dyn SourceManager,
256 expansion_stack: &mut Vec<Arc<Path>>,
257 ) -> Result<SymbolResolution, SymbolResolutionError>
258 where
259 F: Fn(&str) -> Option<AliasTarget>,
260 {
261 if expansion_stack.len() > MAX_ALIAS_EXPANSION_DEPTH {
262 return Err(SymbolResolutionError::alias_expansion_depth_exceeded(
263 path.span(),
264 MAX_ALIAS_EXPANSION_DEPTH,
265 source_manager,
266 ));
267 }
268
269 let path_ref: &Path = *path;
270 if expansion_stack.iter().any(|entry| entry.as_ref() == path_ref) {
271 return Err(SymbolResolutionError::alias_expansion_cycle(path.span(), source_manager));
272 }
273
274 expansion_stack.push(Arc::from(path_ref));
275
276 let result = {
277 let (module_name, rest) = path.split_first().unwrap();
278 if let Some(target) = get_import(module_name) {
279 match target {
280 AliasTarget::MastRoot(digest) if rest.is_empty() => {
281 Ok(SymbolResolution::MastRoot(digest))
282 },
283 AliasTarget::MastRoot(digest) => {
284 Err(SymbolResolutionError::invalid_alias_target(
285 digest.span(),
286 path.span(),
287 source_manager,
288 ))
289 },
290 AliasTarget::Path(shadowed) if shadowed.as_deref() == path => {
299 Ok(SymbolResolution::External(shadowed))
300 },
301 AliasTarget::Path(path) => {
302 let resolved = Self::expand_with_guard(
303 get_import,
304 path.as_deref(),
305 source_manager,
306 expansion_stack,
307 )?;
308 match resolved {
309 SymbolResolution::Module { id, path } => {
310 if rest.is_empty() {
313 Ok(SymbolResolution::Module { id, path })
314 } else {
315 Ok(SymbolResolution::External(
316 path.map(|p| p.join(rest).into()),
317 ))
318 }
319 },
320 SymbolResolution::External(resolved) => {
321 Ok(SymbolResolution::External(
324 resolved.map(|p| p.to_absolute().join(rest).into()),
325 ))
326 },
327 res @ (SymbolResolution::MastRoot(_)
328 | SymbolResolution::Local(_)
329 | SymbolResolution::Exact { .. })
330 if rest.is_empty() =>
331 {
332 Ok(res)
333 },
334 SymbolResolution::MastRoot(digest) => {
335 Err(SymbolResolutionError::invalid_alias_target(
336 digest.span(),
337 path.span(),
338 source_manager,
339 ))
340 },
341 SymbolResolution::Exact { path: item_path, .. } => {
342 Err(SymbolResolutionError::invalid_alias_target(
343 item_path.span(),
344 path.span(),
345 source_manager,
346 ))
347 },
348 SymbolResolution::Local(item) => {
349 Err(SymbolResolutionError::invalid_alias_target(
350 item.span(),
351 path.span(),
352 source_manager,
353 ))
354 },
355 }
356 },
357 }
358 } else {
359 Ok(SymbolResolution::External(path.map(|p| p.to_absolute().into_owned().into())))
362 }
363 };
364
365 expansion_stack.pop();
366 result
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use alloc::{
373 collections::BTreeMap,
374 string::{String, ToString},
375 sync::Arc,
376 };
377 use core::str::FromStr;
378
379 use miden_debug_types::DefaultSourceManager;
380
381 use super::*;
382 use crate::PathBuf;
383
384 fn path_arc(path: &str) -> Arc<Path> {
385 let path = PathBuf::from_str(path).expect("valid path");
386 Arc::from(path.as_path())
387 }
388
389 #[test]
390 fn alias_expansion_detects_cycle() {
391 let source_manager = DefaultSourceManager::default();
392 let mut imports = BTreeMap::<String, AliasTarget>::new();
393 imports.insert("a".to_string(), AliasTarget::Path(Span::unknown(path_arc("b"))));
394 imports.insert("b".to_string(), AliasTarget::Path(Span::unknown(path_arc("a"))));
395
396 let path = PathBuf::from_str("a").expect("valid path");
397 let result = LocalSymbolTable::expand(
398 |name| imports.get(name).cloned(),
399 Span::unknown(path.as_path()),
400 &source_manager,
401 );
402
403 assert!(matches!(result, Err(SymbolResolutionError::AliasExpansionCycle { .. })));
404 }
405
406 #[test]
407 fn alias_expansion_depth_boundary() {
408 let source_manager = DefaultSourceManager::default();
409 let mut imports = BTreeMap::<String, AliasTarget>::new();
410 let max_depth = MAX_ALIAS_EXPANSION_DEPTH;
411 for i in 0..max_depth {
412 let current = format!("a{i}");
413 let next = format!("a{}", i + 1);
414 imports.insert(current, AliasTarget::Path(Span::unknown(path_arc(&next))));
415 }
416
417 let path = PathBuf::from_str("a0").expect("valid path");
418 let result = LocalSymbolTable::expand(
419 |name| imports.get(name).cloned(),
420 Span::unknown(path.as_path()),
421 &source_manager,
422 )
423 .expect("expected depth boundary to resolve");
424
425 match result {
426 SymbolResolution::External(resolved) => {
427 let expected = format!("a{max_depth}");
428 let expected = PathBuf::from_str(&expected).expect("valid path");
429 let expected = expected.as_path().to_absolute().into_owned();
430 assert_eq!(resolved.as_deref(), expected.as_path());
431 },
432 other => panic!("expected external resolution, got {other:?}"),
433 }
434 }
435
436 #[test]
437 fn alias_expansion_depth_exceeded() {
438 let source_manager = DefaultSourceManager::default();
439 let mut imports = BTreeMap::<String, AliasTarget>::new();
440 for i in 0..=MAX_ALIAS_EXPANSION_DEPTH {
441 let current = format!("a{i}");
442 let next = format!("a{}", i + 1);
443 imports.insert(current, AliasTarget::Path(Span::unknown(path_arc(&next))));
444 }
445
446 let path = PathBuf::from_str("a0").expect("valid path");
447 let result = LocalSymbolTable::expand(
448 |name| imports.get(name).cloned(),
449 Span::unknown(path.as_path()),
450 &source_manager,
451 );
452
453 assert!(matches!(
454 result,
455 Err(SymbolResolutionError::AliasExpansionDepthExceeded { max_depth, .. })
456 if max_depth == MAX_ALIAS_EXPANSION_DEPTH
457 ));
458 }
459
460 #[test]
461 fn alias_expansion_handles_shadowed_import() {
462 let source_manager = DefaultSourceManager::default();
463 let mut imports = BTreeMap::<String, AliasTarget>::new();
464 imports.insert("lib".to_string(), AliasTarget::Path(Span::unknown(path_arc("lib"))));
465
466 let path = PathBuf::from_str("lib").expect("valid path");
467 let result = LocalSymbolTable::expand(
468 |name| imports.get(name).cloned(),
469 Span::unknown(path.as_path()),
470 &source_manager,
471 )
472 .expect("shadowed import should resolve");
473
474 match result {
475 SymbolResolution::External(resolved) => {
476 assert_eq!(resolved.as_deref(), path.as_path());
477 },
478 other => panic!("expected external resolution, got {other:?}"),
479 }
480 }
481}