1use std::collections::HashMap;
40
41use crate::ast::{FnDef, TopLevel, TypeDef, TypeVariant};
42use crate::codegen::ModuleInfo;
43use crate::ir::identity::{CtorId, FnId, FnKey, ModuleId, TypeId, TypeKey};
44
45#[derive(Debug, Clone)]
50pub struct FnEntry {
51 pub key: FnKey,
52 pub module: ModuleId,
53 pub index_in_module: u32,
54}
55
56#[derive(Debug, Clone)]
58pub struct TypeEntry {
59 pub key: TypeKey,
60 pub module: ModuleId,
61 pub index_in_module: u32,
62 pub variants: Vec<CtorId>,
66 pub is_product: bool,
72}
73
74#[derive(Debug, Clone)]
78pub struct CtorEntry {
79 pub owning_type: TypeId,
80 pub name: String,
85}
86
87#[derive(Debug, Clone)]
91pub struct ModuleEntry {
92 pub prefix: Option<String>,
94}
95
96#[derive(Debug, Clone, Default)]
101pub struct SymbolTable {
102 pub modules: Vec<ModuleEntry>,
103 pub fns: Vec<FnEntry>,
104 pub types: Vec<TypeEntry>,
105 pub ctors: Vec<CtorEntry>,
106
107 fn_index: HashMap<FnKey, FnId>,
108 type_index: HashMap<TypeKey, TypeId>,
109 ctor_index: HashMap<(TypeId, String), CtorId>,
114}
115
116impl SymbolTable {
117 pub fn build(entry_items: &[TopLevel], dep_modules: &[ModuleInfo]) -> Self {
124 let mut table = SymbolTable::default();
125
126 table.modules.push(ModuleEntry { prefix: None });
128 for m in dep_modules {
129 table.modules.push(ModuleEntry {
130 prefix: Some(m.prefix.clone()),
131 });
132 }
133
134 let entry_fns: Vec<&FnDef> = entry_items
138 .iter()
139 .filter_map(|i| match i {
140 TopLevel::FnDef(fd) => Some(fd),
141 _ => None,
142 })
143 .collect();
144 let entry_types: Vec<&TypeDef> = entry_items
145 .iter()
146 .filter_map(|i| match i {
147 TopLevel::TypeDef(td) => Some(td),
148 _ => None,
149 })
150 .collect();
151
152 type ScopeWalk<'a> = (ModuleId, Option<&'a str>, Vec<&'a FnDef>, Vec<&'a TypeDef>);
153 let scopes: Vec<ScopeWalk> =
154 std::iter::once((ModuleId::ENTRY, None, entry_fns, entry_types))
155 .chain(dep_modules.iter().enumerate().map(|(i, m)| {
156 let module_id = ModuleId((i + 1) as u32);
157 let fns: Vec<&FnDef> = m.fn_defs.iter().collect();
158 let types: Vec<&TypeDef> = m.type_defs.iter().collect();
159 (module_id, Some(m.prefix.as_str()), fns, types)
160 }))
161 .collect();
162
163 for (module_id, prefix, fns, types) in scopes {
164 for (index_in_module, fd) in fns.into_iter().enumerate() {
165 let key = match prefix {
166 Some(p) => FnKey::in_module(p.to_string(), fd.name.clone()),
167 None => FnKey::entry(fd.name.clone()),
168 };
169 let id = FnId(table.fns.len() as u32);
170 table.fn_index.insert(key.clone(), id);
171 table.fns.push(FnEntry {
172 key,
173 module: module_id,
174 index_in_module: index_in_module as u32,
175 });
176 }
177 for (index_in_module, td) in types.into_iter().enumerate() {
178 let (type_name, variants, is_product) = match td {
179 TypeDef::Sum { name, variants, .. } => (name.clone(), variants.clone(), false),
180 TypeDef::Product { name, .. } => (name.clone(), Vec::new(), true),
181 };
182 let key = match prefix {
183 Some(p) => TypeKey::in_module(p.to_string(), type_name.clone()),
184 None => TypeKey::entry(type_name.clone()),
185 };
186 let type_id = TypeId(table.types.len() as u32);
187 table.type_index.insert(key.clone(), type_id);
188 let ctor_ids: Vec<CtorId> = if is_product {
191 let cid = CtorId(table.ctors.len() as u32);
192 table.ctors.push(CtorEntry {
193 owning_type: type_id,
194 name: type_name.clone(),
195 });
196 table.ctor_index.insert((type_id, type_name.clone()), cid);
197 vec![cid]
198 } else {
199 let mut ids = Vec::with_capacity(variants.len());
200 for v in &variants {
201 let cid = CtorId(table.ctors.len() as u32);
202 table.ctors.push(CtorEntry {
203 owning_type: type_id,
204 name: v.name.clone(),
205 });
206 table.ctor_index.insert((type_id, v.name.clone()), cid);
207 ids.push(cid);
208 }
209 ids
210 };
211 table.types.push(TypeEntry {
212 key,
213 module: module_id,
214 index_in_module: index_in_module as u32,
215 variants: ctor_ids,
216 is_product,
217 });
218 }
219 }
220
221 table
222 }
223
224 pub fn fn_id_of(&self, key: &FnKey) -> Option<FnId> {
227 self.fn_index.get(key).copied()
228 }
229
230 pub fn type_id_of(&self, key: &TypeKey) -> Option<TypeId> {
232 self.type_index.get(key).copied()
233 }
234
235 pub fn type_id_by_bare_name(&self, name: &str) -> Option<TypeId> {
251 let mut found: Option<TypeId> = None;
252 for (key, id) in &self.type_index {
253 if key.name == name {
254 if found.is_some() {
255 return None;
258 }
259 found = Some(*id);
260 }
261 }
262 found
263 }
264
265 pub fn ctor_id_of(&self, owning_type: TypeId, variant: &str) -> Option<CtorId> {
267 self.ctor_index
268 .get(&(owning_type, variant.to_string()))
269 .copied()
270 }
271
272 pub fn fn_entry(&self, id: FnId) -> &FnEntry {
277 &self.fns[id.0 as usize]
278 }
279
280 pub fn type_entry(&self, id: TypeId) -> &TypeEntry {
281 &self.types[id.0 as usize]
282 }
283
284 pub fn ctor_entry(&self, id: CtorId) -> &CtorEntry {
285 &self.ctors[id.0 as usize]
286 }
287
288 pub fn module_entry(&self, id: ModuleId) -> &ModuleEntry {
289 &self.modules[id.0 as usize]
290 }
291
292 #[cfg(test)]
297 pub(crate) fn assert_consistent(&self) {
298 for (i, entry) in self.fns.iter().enumerate() {
299 assert_eq!(
300 self.fn_index.get(&entry.key),
301 Some(&FnId(i as u32)),
302 "fn_index out of sync at index {i}"
303 );
304 }
305 for (i, entry) in self.types.iter().enumerate() {
306 assert_eq!(
307 self.type_index.get(&entry.key),
308 Some(&TypeId(i as u32)),
309 "type_index out of sync at index {i}"
310 );
311 }
312 for (i, entry) in self.ctors.iter().enumerate() {
313 assert_eq!(
314 self.ctor_index
315 .get(&(entry.owning_type, entry.name.clone())),
316 Some(&CtorId(i as u32)),
317 "ctor_index out of sync at index {i}"
318 );
319 }
320 }
321}
322
323#[allow(dead_code)] fn _no_warning_for_unused_variant_field(_v: &TypeVariant) {}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use crate::ast::{TypeDef, TypeVariant};
330
331 fn product(name: &str, line: usize) -> TypeDef {
332 TypeDef::Product {
333 name: name.to_string(),
334 fields: vec![("value".to_string(), "Int".to_string())],
335 line,
336 }
337 }
338
339 fn sum(name: &str, variants: &[&str], line: usize) -> TypeDef {
340 TypeDef::Sum {
341 name: name.to_string(),
342 variants: variants
343 .iter()
344 .map(|v| TypeVariant {
345 name: v.to_string(),
346 fields: Vec::new(),
347 })
348 .collect(),
349 line,
350 }
351 }
352
353 fn module(prefix: &str, fns: Vec<FnDef>, types: Vec<TypeDef>) -> ModuleInfo {
354 ModuleInfo {
355 prefix: prefix.to_string(),
356 depends: Vec::new(),
357 type_defs: types,
358 fn_defs: fns,
359 analysis: None,
360 }
361 }
362
363 fn fn_def(name: &str) -> FnDef {
364 use crate::ast::{FnBody, Literal, Spanned};
365 FnDef {
366 name: name.to_string(),
367 line: 1,
368 params: Vec::new(),
369 return_type: "Int".to_string(),
370 effects: Vec::new(),
371 desc: None,
372 body: std::sync::Arc::new(FnBody::from_expr(Spanned::new(
373 crate::ast::Expr::Literal(Literal::Int(0)),
374 1,
375 ))),
376 resolution: None,
377 }
378 }
379
380 #[test]
381 fn entry_only_program_gets_entry_module_and_indexed_fns() {
382 let items = vec![
383 TopLevel::FnDef(fn_def("foo")),
384 TopLevel::FnDef(fn_def("bar")),
385 ];
386 let table = SymbolTable::build(&items, &[]);
387 table.assert_consistent();
388
389 assert_eq!(table.modules.len(), 1, "entry-only build has just ENTRY");
390 assert!(table.modules[0].prefix.is_none());
391
392 let foo_id = table.fn_id_of(&FnKey::entry("foo")).expect("foo missing");
393 let bar_id = table.fn_id_of(&FnKey::entry("bar")).expect("bar missing");
394 assert_ne!(foo_id, bar_id);
395 assert!(table.fn_entry(foo_id).module.is_entry());
396 }
397
398 #[test]
399 fn cross_module_same_bare_name_fns_get_distinct_ids() {
400 let mod_a = module("A", vec![fn_def("foo")], vec![]);
404 let mod_b = module("B", vec![fn_def("foo")], vec![]);
405 let table = SymbolTable::build(&[], &[mod_a, mod_b]);
406 table.assert_consistent();
407
408 let a_foo = table
409 .fn_id_of(&FnKey::in_module("A", "foo"))
410 .expect("A.foo missing");
411 let b_foo = table
412 .fn_id_of(&FnKey::in_module("B", "foo"))
413 .expect("B.foo missing");
414 assert_ne!(a_foo, b_foo);
415
416 assert!(
419 table.fn_id_of(&FnKey::entry("foo")).is_none(),
420 "bare-entry `foo` must miss when only module fns named `foo` exist"
421 );
422 }
423
424 #[test]
425 fn product_type_has_one_constructor_under_its_own_name() {
426 let items = vec![TopLevel::TypeDef(product("Natural", 1))];
427 let table = SymbolTable::build(&items, &[]);
428 table.assert_consistent();
429
430 let nat_id = table.type_id_of(&TypeKey::entry("Natural")).unwrap();
431 let entry = table.type_entry(nat_id);
432 assert!(entry.is_product);
433 assert_eq!(entry.variants.len(), 1);
434 let ctor_id = entry.variants[0];
435 assert_eq!(table.ctor_entry(ctor_id).name, "Natural");
436 assert_eq!(table.ctor_entry(ctor_id).owning_type, nat_id);
437 assert_eq!(table.ctor_id_of(nat_id, "Natural"), Some(ctor_id));
438 }
439
440 #[test]
441 fn sum_type_registers_each_variant_under_its_type() {
442 let items = vec![TopLevel::TypeDef(sum("Shape", &["Circle", "Square"], 1))];
443 let table = SymbolTable::build(&items, &[]);
444 table.assert_consistent();
445
446 let shape_id = table.type_id_of(&TypeKey::entry("Shape")).unwrap();
447 let entry = table.type_entry(shape_id);
448 assert!(!entry.is_product);
449 assert_eq!(entry.variants.len(), 2);
450 let circle = table.ctor_id_of(shape_id, "Circle").unwrap();
451 let square = table.ctor_id_of(shape_id, "Square").unwrap();
452 assert_ne!(circle, square);
453 assert_eq!(table.ctor_entry(circle).name, "Circle");
454 assert_eq!(table.ctor_entry(square).name, "Square");
455 }
456
457 #[test]
458 fn variant_name_collision_across_unrelated_sums_is_resolved_per_type() {
459 let items = vec![
465 TopLevel::TypeDef(sum("Validation", &["Ok", "Invalid"], 1)),
466 TopLevel::TypeDef(sum("Status", &["Ok", "Failed"], 5)),
467 ];
468 let table = SymbolTable::build(&items, &[]);
469 table.assert_consistent();
470
471 let v_id = table.type_id_of(&TypeKey::entry("Validation")).unwrap();
472 let s_id = table.type_id_of(&TypeKey::entry("Status")).unwrap();
473 let v_ok = table.ctor_id_of(v_id, "Ok").unwrap();
474 let s_ok = table.ctor_id_of(s_id, "Ok").unwrap();
475 assert_ne!(
476 v_ok, s_ok,
477 "same-bare-name variants across unrelated sums must be distinct"
478 );
479 }
480
481 #[test]
482 fn deterministic_indexing_across_repeated_builds() {
483 let items_a = vec![
487 TopLevel::FnDef(fn_def("a")),
488 TopLevel::FnDef(fn_def("b")),
489 TopLevel::TypeDef(product("R", 1)),
490 ];
491 let items_b = items_a.clone();
492 let t1 = SymbolTable::build(&items_a, &[]);
493 let t2 = SymbolTable::build(&items_b, &[]);
494 assert_eq!(
495 t1.fn_id_of(&FnKey::entry("a")),
496 t2.fn_id_of(&FnKey::entry("a"))
497 );
498 assert_eq!(
499 t1.type_id_of(&TypeKey::entry("R")),
500 t2.type_id_of(&TypeKey::entry("R"))
501 );
502 }
503
504 #[test]
505 fn entry_scope_is_always_module_id_zero() {
506 let mod_a = module("A", vec![fn_def("x")], vec![]);
509 let mod_b = module("B", vec![fn_def("y")], vec![]);
510 let table = SymbolTable::build(&[TopLevel::FnDef(fn_def("entry_fn"))], &[mod_a, mod_b]);
511 table.assert_consistent();
512 let entry_fn = table.fn_id_of(&FnKey::entry("entry_fn")).unwrap();
513 assert_eq!(table.fn_entry(entry_fn).module, ModuleId::ENTRY);
514 assert_eq!(table.module_entry(ModuleId::ENTRY).prefix, None);
515 }
516
517 #[test]
518 fn type_id_by_bare_name_resolves_unique_cross_module_type() {
519 let mod_a = module("A", vec![], vec![sum("Color", &["Red", "Blue"], 1)]);
525 let mod_b = module("B", vec![], vec![product("Val", 1)]);
526 let table = SymbolTable::build(&[], &[mod_a, mod_b]);
527 table.assert_consistent();
528
529 let val_id = table.type_id_by_bare_name("Val");
530 let qualified = table.type_id_of(&TypeKey::in_module("B", "Val"));
531 assert_eq!(val_id, qualified, "bare `Val` resolves to B.Val");
532 assert!(val_id.is_some());
533
534 let color_id = table.type_id_by_bare_name("Color");
535 assert_eq!(
536 color_id,
537 table.type_id_of(&TypeKey::in_module("A", "Color"))
538 );
539 }
540
541 #[test]
542 fn type_id_by_bare_name_returns_none_on_ambiguity() {
543 let mod_a = module("A", vec![], vec![product("T", 1)]);
551 let mod_b = module("B", vec![], vec![product("T", 1)]);
552 let table = SymbolTable::build(&[], &[mod_a, mod_b]);
553 table.assert_consistent();
554
555 assert_eq!(table.type_id_by_bare_name("T"), None);
556
557 assert!(table.type_id_of(&TypeKey::in_module("A", "T")).is_some());
559 assert!(table.type_id_of(&TypeKey::in_module("B", "T")).is_some());
560 }
561
562 #[test]
563 fn type_id_by_bare_name_finds_entry_scope_type() {
564 let entry_type = product("Cfg", 1);
567 let mod_a = module("A", vec![], vec![sum("Other", &["X"], 1)]);
568 let table = SymbolTable::build(&[TopLevel::TypeDef(entry_type)], &[mod_a]);
569 table.assert_consistent();
570
571 let cfg = table.type_id_by_bare_name("Cfg");
572 assert_eq!(cfg, table.type_id_of(&TypeKey::entry("Cfg")));
573 assert!(cfg.is_some());
574 }
575
576 #[test]
577 fn type_id_by_bare_name_missing_name_is_none() {
578 let mod_a = module("A", vec![], vec![product("X", 1)]);
579 let table = SymbolTable::build(&[], &[mod_a]);
580 table.assert_consistent();
581 assert_eq!(table.type_id_by_bare_name("NotATypeAnywhere"), None);
582 }
583}