miden_assembly_syntax/ast/item/resolver/
symbol_table.rs1use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
2
3use miden_debug_types::{SourceManager, SourceSpan, Span, Spanned};
4
5use super::{SymbolResolution, SymbolResolutionError};
6use crate::ast::{Ident, Import, ItemIndex};
7
8pub trait SymbolTable {
13 type SymbolIter: Iterator<Item = LocalSymbol>;
15
16 fn symbols(&self, source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter;
19
20 fn checked_symbols(
26 &self,
27 source_manager: Arc<dyn SourceManager>,
28 ) -> Result<Self::SymbolIter, SymbolResolutionError> {
29 Ok(self.symbols(source_manager))
30 }
31}
32
33impl SymbolTable for &crate::module::ModuleInfo {
34 type SymbolIter = alloc::vec::IntoIter<LocalSymbol>;
35
36 fn symbols(&self, _source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter {
37 let mut items = Vec::with_capacity(self.raw_items().len());
38
39 for (i, item) in self.raw_items().iter().enumerate() {
40 let name = item.name().clone();
41 let span = name.span();
42 items.push(LocalSymbol::Item {
43 name,
44 resolved: SymbolResolution::Local(Span::new(span, ItemIndex::new(i))),
45 });
46 }
47
48 items.into_iter()
49 }
50
51 fn checked_symbols(
52 &self,
53 source_manager: Arc<dyn SourceManager>,
54 ) -> Result<Self::SymbolIter, SymbolResolutionError> {
55 if self.raw_items().len() > ItemIndex::MAX_ITEMS {
56 Err(SymbolResolutionError::too_many_items_in_module(
57 SourceSpan::UNKNOWN,
58 &*source_manager,
59 ))
60 } else {
61 Ok(self.symbols(source_manager))
62 }
63 }
64}
65
66impl SymbolTable for &crate::ast::Module {
67 type SymbolIter = alloc::vec::IntoIter<LocalSymbol>;
68
69 fn symbols(&self, _source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter {
70 let mut items = Vec::with_capacity(self.items.len() + self.imports.len());
71
72 for (i, item) in self.items.iter().enumerate() {
73 let id = ItemIndex::new(i);
74 let name = item.name().clone();
75 let span = name.span();
76 let name = name.into_inner();
77
78 items.push(LocalSymbol::Item {
79 name: Ident::from_raw_parts(Span::new(span, name)),
80 resolved: SymbolResolution::Local(Span::new(span, id)),
81 });
82 }
83
84 items.extend(self.imports.iter().filter_map(|import| {
85 let Import::Item(item) = import else {
86 return None;
87 };
88 let local_name = import.local_name().clone();
89 let span = local_name.span();
90 let name = Span::new(span, local_name.into_inner());
91 Some(LocalSymbol::Import {
92 name,
93 resolution: Ok(SymbolResolution::External(item.target_path())),
94 })
95 }));
96
97 items.into_iter()
98 }
99
100 fn checked_symbols(
101 &self,
102 source_manager: Arc<dyn SourceManager>,
103 ) -> Result<Self::SymbolIter, SymbolResolutionError> {
104 if self.items.len() + self.imports.len() > ItemIndex::MAX_ITEMS {
105 Err(SymbolResolutionError::too_many_items_in_module(self.span(), &*source_manager))
106 } else {
107 Ok(self.symbols(source_manager))
108 }
109 }
110}
111
112#[derive(Debug)]
114pub enum LocalSymbol {
115 Item { name: Ident, resolved: SymbolResolution },
117 Import {
119 name: Span<Arc<str>>,
120 resolution: Result<SymbolResolution, SymbolResolutionError>,
121 },
122}
123
124impl LocalSymbol {
125 pub fn name(&self) -> &str {
126 match self {
127 Self::Item { name, .. } => name.as_str(),
128 Self::Import { name, .. } => name,
129 }
130 }
131}
132
133pub(super) struct LocalSymbolTable {
135 source_manager: Arc<dyn SourceManager>,
136 symbols: BTreeMap<Arc<str>, ItemIndex>,
137 items: Vec<LocalSymbol>,
138}
139
140impl core::ops::Index<ItemIndex> for LocalSymbolTable {
141 type Output = LocalSymbol;
142
143 #[inline(always)]
144 fn index(&self, index: ItemIndex) -> &Self::Output {
145 &self.items[index.as_usize()]
146 }
147}
148
149impl LocalSymbolTable {
150 fn build<I>(iter: I, source_manager: Arc<dyn SourceManager>) -> Self
151 where
152 I: Iterator<Item = LocalSymbol>,
153 {
154 let mut symbols = BTreeMap::default();
155 let mut items = Vec::with_capacity(16);
156
157 for (i, symbol) in iter.enumerate() {
158 let id = ItemIndex::try_new(i)
159 .expect("symbol iterators used by LocalSymbolTable::build must be pre-validated");
160 let symbol = match symbol {
161 LocalSymbol::Item {
162 name,
163 resolved: SymbolResolution::Local(local),
164 } => LocalSymbol::Item {
165 name,
166 resolved: SymbolResolution::Local(Span::new(local.span(), id)),
167 },
168 symbol => symbol,
169 };
170 log::debug!(target: "symbol-table::new", "registering {} symbol: {}", match symbol {
171 LocalSymbol::Item { .. } => "local",
172 LocalSymbol::Import { .. } => "imported",
173 }, symbol.name());
174 let name = match &symbol {
175 LocalSymbol::Item { name, .. } => name.clone().into_inner(),
176 LocalSymbol::Import { name, .. } => name.clone().into_inner(),
177 };
178
179 if let Some(prev) = symbols.get(&name).copied() {
180 debug_assert!(
181 false,
182 "duplicate symbol '{name}' reached local resolver construction (previous={prev:?}, current={id:?})"
183 );
184 } else {
185 symbols.insert(name.clone(), id);
186 }
187 items.push(symbol);
188 }
189
190 Self { source_manager, symbols, items }
191 }
192
193 pub fn new<S>(
194 iter: S,
195 source_manager: Arc<dyn SourceManager>,
196 ) -> Result<Self, SymbolResolutionError>
197 where
198 S: SymbolTable,
199 {
200 let symbols = iter.checked_symbols(source_manager.clone())?;
201 Ok(Self::build(symbols, source_manager))
202 }
203}
204
205impl LocalSymbolTable {
206 pub fn get(&self, name: Span<&str>) -> Result<SymbolResolution, SymbolResolutionError> {
216 log::debug!(target: "symbol-table", "attempting to resolve '{name}'");
217 let (span, name) = name.into_parts();
218 let Some(item) = self.symbols.get(name).copied() else {
219 return Err(SymbolResolutionError::undefined(span, &self.source_manager));
220 };
221 match &self.items[item.as_usize()] {
222 LocalSymbol::Item { resolved, .. } => {
223 log::debug!(target: "symbol-table", "resolved '{name}' to {resolved:?}");
224 Ok(resolved.clone())
225 },
226 LocalSymbol::Import { name, resolution } => {
227 log::debug!(target: "symbol-table", "'{name}' refers to an import");
228 match resolution {
229 Ok(resolved) => {
230 log::debug!(target: "symbol-table", "resolved '{name}' to {resolved:?}");
231 Ok(resolved.clone())
232 },
233 Err(err) => {
234 log::error!(target: "symbol-table", "resolution of '{name}' failed: {err}");
235 Err(err.clone())
236 },
237 }
238 },
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use alloc::sync::Arc;
246
247 use miden_debug_types::DefaultSourceManager;
248
249 use super::*;
250 use crate::Path;
251
252 #[test]
253 fn checked_symbols_rejects_oversized_module() {
254 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
255 let mut module =
256 crate::ast::Module::new(crate::ast::ModuleKind::Library, Path::new("::m::huge"));
257
258 for i in 0..=ItemIndex::MAX_ITEMS {
259 module.items.push(crate::ast::Item::Constant(crate::ast::Constant::new(
260 SourceSpan::UNKNOWN,
261 crate::ast::Visibility::Private,
262 Ident::new(format!("A{i}")).expect("valid identifier"),
263 crate::ast::ConstantExpr::Int(Span::unknown(crate::parser::IntValue::from(0u8))),
264 )));
265 }
266
267 let result = (&module).checked_symbols(source_manager);
268
269 assert!(matches!(result, Err(SymbolResolutionError::TooManyItemsInModule { .. })));
270 }
271
272 #[test]
273 fn checked_symbols_guard_custom_symbol_table_exact() {
274 struct ExactTooManySymbols;
275
276 impl SymbolTable for ExactTooManySymbols {
277 type SymbolIter = alloc::vec::IntoIter<LocalSymbol>;
278
279 fn symbols(&self, _source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter {
280 panic!("exact construction must not request unchecked symbols")
281 }
282
283 fn checked_symbols(
284 &self,
285 source_manager: Arc<dyn SourceManager>,
286 ) -> Result<Self::SymbolIter, SymbolResolutionError> {
287 Err(SymbolResolutionError::too_many_items_in_module(
288 SourceSpan::UNKNOWN,
289 &*source_manager,
290 ))
291 }
292 }
293
294 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
295 let result = LocalSymbolTable::new(ExactTooManySymbols, source_manager);
296
297 assert!(matches!(result, Err(SymbolResolutionError::TooManyItemsInModule { .. })));
298 }
299
300 #[cfg(test)]
301 struct DuplicateSymbolsForInvariantTest;
302
303 #[cfg(test)]
304 impl SymbolTable for DuplicateSymbolsForInvariantTest {
305 type SymbolIter = alloc::vec::IntoIter<LocalSymbol>;
306
307 fn symbols(&self, _source_manager: Arc<dyn SourceManager>) -> Self::SymbolIter {
308 let first = LocalSymbol::Item {
309 name: Ident::new("dup").expect("valid identifier"),
310 resolved: SymbolResolution::Local(Span::unknown(ItemIndex::new(0))),
311 };
312 let second = LocalSymbol::Item {
313 name: Ident::new("dup").expect("valid identifier"),
314 resolved: SymbolResolution::Local(Span::unknown(ItemIndex::new(1))),
315 };
316 alloc::vec![first, second].into_iter()
317 }
318 }
319
320 #[cfg(debug_assertions)]
321 #[test]
322 #[should_panic(expected = "duplicate symbol 'dup' reached local resolver construction")]
323 fn local_symbol_table_rejects_duplicate_symbols() {
324 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
325 let _table = LocalSymbolTable::new(DuplicateSymbolsForInvariantTest, source_manager);
326 }
327
328 #[test]
329 fn local_symbol_table_duplicate_symbols_have_explicit_behavior() {
330 use std::panic::{AssertUnwindSafe, catch_unwind};
331
332 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
333 let result = catch_unwind(AssertUnwindSafe(|| {
334 LocalSymbolTable::new(DuplicateSymbolsForInvariantTest, source_manager)
335 }));
336
337 if cfg!(debug_assertions) {
338 assert!(
339 result.is_err(),
340 "debug builds should panic when duplicates reach local resolver construction"
341 );
342 } else {
343 let table = result
344 .expect("release builds should not panic on duplicate symbols")
345 .expect("release builds should still construct a table");
346 let resolved = table
347 .get(Span::unknown("dup"))
348 .expect("release behavior should keep a deterministic symbol mapping");
349 match resolved {
350 SymbolResolution::Local(id) => assert_eq!(id.into_inner(), ItemIndex::new(0)),
351 other => panic!("expected local symbol resolution, got {other:?}"),
352 }
353 }
354 }
355}