1use std::collections::HashMap;
16
17use bock_ast::Visibility;
18
19use crate::stubs::TypeRef;
20
21pub type ModuleId = String;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum RegistryError {
31 ModuleNotFound { module_id: String },
33 SymbolNotFound { module_id: String, name: String },
35 NotVisible { module_id: String, name: String },
37}
38
39impl std::fmt::Display for RegistryError {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 RegistryError::ModuleNotFound { module_id } => {
43 write!(f, "module not found: `{module_id}`")
44 }
45 RegistryError::SymbolNotFound { module_id, name } => {
46 write!(f, "symbol `{name}` not found in module `{module_id}`")
47 }
48 RegistryError::NotVisible { module_id, name } => {
49 write!(
50 f,
51 "symbol `{name}` in module `{module_id}` is not visible"
52 )
53 }
54 }
55 }
56}
57
58impl std::error::Error for RegistryError {}
59
60#[derive(Debug, Default)]
67pub struct ModuleRegistry {
68 modules: HashMap<ModuleId, ModuleExports>,
70}
71
72impl ModuleRegistry {
73 #[must_use]
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn register(&mut self, exports: ModuleExports) {
83 self.modules.insert(exports.module_id.clone(), exports);
84 }
85
86 #[must_use]
88 pub fn has_module(&self, module_id: &str) -> bool {
89 self.modules.contains_key(module_id)
90 }
91
92 #[must_use]
94 pub fn get_module(&self, module_id: &str) -> Option<&ModuleExports> {
95 self.modules.get(module_id)
96 }
97
98 pub fn resolve_symbol(
106 &self,
107 module_id: &str,
108 name: &str,
109 ) -> Result<&ExportedSymbol, RegistryError> {
110 let exports = self
111 .modules
112 .get(module_id)
113 .ok_or_else(|| RegistryError::ModuleNotFound {
114 module_id: module_id.to_string(),
115 })?;
116
117 if let Some(sym) = exports.symbols.get(name) {
119 return if sym.visibility == Visibility::Private {
120 Err(RegistryError::NotVisible {
121 module_id: module_id.to_string(),
122 name: name.to_string(),
123 })
124 } else {
125 Ok(sym)
126 };
127 }
128
129 if let Some((source_module, original_name)) = exports.reexports.get(name) {
131 return self.resolve_symbol(source_module, original_name);
132 }
133
134 Err(RegistryError::SymbolNotFound {
135 module_id: module_id.to_string(),
136 name: name.to_string(),
137 })
138 }
139
140 pub fn resolve_glob(
145 &self,
146 module_id: &str,
147 ) -> Result<Vec<(&str, &ExportedSymbol)>, RegistryError> {
148 let exports = self
149 .modules
150 .get(module_id)
151 .ok_or_else(|| RegistryError::ModuleNotFound {
152 module_id: module_id.to_string(),
153 })?;
154
155 let mut result: Vec<(&str, &ExportedSymbol)> = exports
156 .symbols
157 .iter()
158 .filter(|(_, sym)| sym.visibility != Visibility::Private)
159 .map(|(name, sym)| (name.as_str(), sym))
160 .collect();
161
162 for (local_name, (source_module, original_name)) in &exports.reexports {
164 if let Ok(sym) = self.resolve_symbol(source_module, original_name) {
165 result.push((local_name.as_str(), sym));
166 }
167 }
168
169 result.sort_by_key(|(name, _)| *name);
170 Ok(result)
171 }
172
173 pub fn get_type(
178 &self,
179 module_id: &str,
180 name: &str,
181 ) -> Result<&TypeRef, RegistryError> {
182 self.resolve_symbol(module_id, name).map(|sym| &sym.ty)
183 }
184
185 #[must_use]
187 pub fn module_count(&self) -> usize {
188 self.modules.len()
189 }
190}
191
192#[derive(Debug, Clone)]
196pub struct ModuleExports {
197 pub module_id: ModuleId,
199 pub source_path: String,
201 pub symbols: HashMap<String, ExportedSymbol>,
203 pub reexports: HashMap<String, (ModuleId, String)>,
206}
207
208impl ModuleExports {
209 #[must_use]
211 pub fn new(module_id: impl Into<String>, source_path: impl Into<String>) -> Self {
212 Self {
213 module_id: module_id.into(),
214 source_path: source_path.into(),
215 symbols: HashMap::new(),
216 reexports: HashMap::new(),
217 }
218 }
219
220 pub fn add_symbol(&mut self, name: impl Into<String>, symbol: ExportedSymbol) {
222 self.symbols.insert(name.into(), symbol);
223 }
224
225 pub fn add_reexport(
227 &mut self,
228 local_name: impl Into<String>,
229 source_module: impl Into<String>,
230 original_name: impl Into<String>,
231 ) {
232 self.reexports
233 .insert(local_name.into(), (source_module.into(), original_name.into()));
234 }
235}
236
237#[derive(Debug, Clone)]
241pub struct ExportedSymbol {
242 pub kind: ExportKind,
244 pub visibility: Visibility,
246 pub ty: TypeRef,
251 pub detail: ExportDetail,
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub enum ExportKind {
260 Function,
262 Record,
264 Enum,
266 Trait,
268 Effect,
270 TypeAlias,
272 Constant,
274}
275
276#[derive(Debug, Clone)]
280pub enum ExportDetail {
281 None,
283
284 Record {
286 fields: Vec<(String, TypeRef)>,
288 generic_params: Vec<String>,
290 methods: HashMap<String, TypeRef>,
292 },
293
294 Enum {
296 variants: Vec<EnumVariantExport>,
298 generic_params: Vec<String>,
300 },
301
302 Trait {
304 methods: HashMap<String, TypeRef>,
306 },
307
308 Effect {
310 operations: Vec<(String, TypeRef)>,
312 components: Vec<String>,
314 },
315
316 TypeAlias {
318 underlying: TypeRef,
320 },
321}
322
323#[derive(Debug, Clone)]
327pub struct EnumVariantExport {
328 pub name: String,
330 pub constructor_type: Option<TypeRef>,
333 pub fields: Option<Vec<(String, TypeRef)>>,
336}
337
338#[cfg(test)]
341mod tests {
342 use super::*;
343
344 fn make_fn_symbol(name: &str, vis: Visibility) -> (String, ExportedSymbol) {
347 (
348 name.to_string(),
349 ExportedSymbol {
350 kind: ExportKind::Function,
351 visibility: vis,
352 ty: TypeRef(format!("Fn() -> Void")),
353 detail: ExportDetail::None,
354 },
355 )
356 }
357
358 fn make_record_symbol(
359 name: &str,
360 vis: Visibility,
361 fields: Vec<(&str, &str)>,
362 generics: Vec<&str>,
363 ) -> (String, ExportedSymbol) {
364 (
365 name.to_string(),
366 ExportedSymbol {
367 kind: ExportKind::Record,
368 visibility: vis,
369 ty: TypeRef(name.to_string()),
370 detail: ExportDetail::Record {
371 fields: fields
372 .into_iter()
373 .map(|(n, t)| (n.to_string(), TypeRef(t.to_string())))
374 .collect(),
375 generic_params: generics.into_iter().map(String::from).collect(),
376 methods: HashMap::new(),
377 },
378 },
379 )
380 }
381
382 fn make_enum_symbol(
383 name: &str,
384 vis: Visibility,
385 variants: Vec<EnumVariantExport>,
386 generics: Vec<&str>,
387 ) -> (String, ExportedSymbol) {
388 (
389 name.to_string(),
390 ExportedSymbol {
391 kind: ExportKind::Enum,
392 visibility: vis,
393 ty: TypeRef(name.to_string()),
394 detail: ExportDetail::Enum {
395 variants,
396 generic_params: generics.into_iter().map(String::from).collect(),
397 },
398 },
399 )
400 }
401
402 fn sample_module() -> ModuleExports {
403 let mut exports = ModuleExports::new("app.models", "src/app/models.bock");
404
405 let (name, sym) = make_fn_symbol("create_user", Visibility::Public);
407 exports.add_symbol(name, sym);
408
409 let (name, sym) = make_record_symbol(
411 "User",
412 Visibility::Public,
413 vec![("id", "Int"), ("name", "String")],
414 vec![],
415 );
416 exports.add_symbol(name, sym);
417
418 let (name, sym) = make_enum_symbol(
420 "Status",
421 Visibility::Public,
422 vec![
423 EnumVariantExport {
424 name: "Active".to_string(),
425 constructor_type: None,
426 fields: None,
427 },
428 EnumVariantExport {
429 name: "Suspended".to_string(),
430 constructor_type: Some(TypeRef("Fn(String) -> Status".to_string())),
431 fields: None,
432 },
433 ],
434 vec![],
435 );
436 exports.add_symbol(name, sym);
437
438 let (name, sym) = make_fn_symbol("hash_password", Visibility::Private);
440 exports.add_symbol(name, sym);
441
442 let (name, sym) = make_fn_symbol("validate_email", Visibility::Internal);
444 exports.add_symbol(name, sym);
445
446 exports
447 }
448
449 #[test]
452 fn register_and_lookup_module() {
453 let mut reg = ModuleRegistry::new();
454 assert!(!reg.has_module("app.models"));
455 assert_eq!(reg.module_count(), 0);
456
457 reg.register(sample_module());
458
459 assert!(reg.has_module("app.models"));
460 assert_eq!(reg.module_count(), 1);
461
462 let m = reg.get_module("app.models").unwrap();
463 assert_eq!(m.module_id, "app.models");
464 assert_eq!(m.source_path, "src/app/models.bock");
465 }
466
467 #[test]
470 fn resolve_public_function() {
471 let mut reg = ModuleRegistry::new();
472 reg.register(sample_module());
473
474 let sym = reg.resolve_symbol("app.models", "create_user").unwrap();
475 assert_eq!(sym.kind, ExportKind::Function);
476 assert_eq!(sym.visibility, Visibility::Public);
477 }
478
479 #[test]
480 fn resolve_public_record() {
481 let mut reg = ModuleRegistry::new();
482 reg.register(sample_module());
483
484 let sym = reg.resolve_symbol("app.models", "User").unwrap();
485 assert_eq!(sym.kind, ExportKind::Record);
486 match &sym.detail {
487 ExportDetail::Record { fields, generic_params, .. } => {
488 assert_eq!(fields.len(), 2);
489 assert_eq!(fields[0].0, "id");
490 assert!(generic_params.is_empty());
491 }
492 _ => panic!("expected Record detail"),
493 }
494 }
495
496 #[test]
497 fn resolve_public_enum() {
498 let mut reg = ModuleRegistry::new();
499 reg.register(sample_module());
500
501 let sym = reg.resolve_symbol("app.models", "Status").unwrap();
502 assert_eq!(sym.kind, ExportKind::Enum);
503 match &sym.detail {
504 ExportDetail::Enum { variants, .. } => {
505 assert_eq!(variants.len(), 2);
506 assert_eq!(variants[0].name, "Active");
507 assert!(variants[0].constructor_type.is_none());
508 assert_eq!(variants[1].name, "Suspended");
509 assert!(variants[1].constructor_type.is_some());
510 }
511 _ => panic!("expected Enum detail"),
512 }
513 }
514
515 #[test]
516 fn resolve_internal_symbol() {
517 let mut reg = ModuleRegistry::new();
518 reg.register(sample_module());
519
520 let sym = reg.resolve_symbol("app.models", "validate_email").unwrap();
521 assert_eq!(sym.kind, ExportKind::Function);
522 assert_eq!(sym.visibility, Visibility::Internal);
523 }
524
525 #[test]
528 fn resolve_glob_excludes_private() {
529 let mut reg = ModuleRegistry::new();
530 reg.register(sample_module());
531
532 let syms = reg.resolve_glob("app.models").unwrap();
533 let names: Vec<&str> = syms.iter().map(|(n, _)| *n).collect();
534
535 assert!(names.contains(&"create_user"));
537 assert!(names.contains(&"User"));
538 assert!(names.contains(&"Status"));
539 assert!(names.contains(&"validate_email"));
540 assert!(!names.contains(&"hash_password"));
541 }
542
543 #[test]
544 fn resolve_glob_includes_reexports() {
545 let mut reg = ModuleRegistry::new();
546
547 let mut upstream = ModuleExports::new("lib.utils", "src/lib/utils.bock");
549 let (name, sym) = make_fn_symbol("format_date", Visibility::Public);
550 upstream.add_symbol(name, sym);
551 reg.register(upstream);
552
553 let mut downstream = ModuleExports::new("app.helpers", "src/app/helpers.bock");
555 let (name, sym) = make_fn_symbol("helper_fn", Visibility::Public);
556 downstream.add_symbol(name, sym);
557 downstream.add_reexport("format_date", "lib.utils", "format_date");
558 reg.register(downstream);
559
560 let syms = reg.resolve_glob("app.helpers").unwrap();
561 let names: Vec<&str> = syms.iter().map(|(n, _)| *n).collect();
562
563 assert!(names.contains(&"helper_fn"));
564 assert!(names.contains(&"format_date"));
565 }
566
567 #[test]
570 fn missing_module_error() {
571 let reg = ModuleRegistry::new();
572
573 let err = reg.resolve_symbol("no.such.module", "foo").unwrap_err();
574 assert_eq!(
575 err,
576 RegistryError::ModuleNotFound {
577 module_id: "no.such.module".to_string(),
578 }
579 );
580 }
581
582 #[test]
583 fn missing_module_glob_error() {
584 let reg = ModuleRegistry::new();
585
586 let err = reg.resolve_glob("no.such.module").unwrap_err();
587 assert_eq!(
588 err,
589 RegistryError::ModuleNotFound {
590 module_id: "no.such.module".to_string(),
591 }
592 );
593 }
594
595 #[test]
596 fn missing_module_get_type_error() {
597 let reg = ModuleRegistry::new();
598
599 let err = reg.get_type("no.such.module", "Foo").unwrap_err();
600 assert!(matches!(err, RegistryError::ModuleNotFound { .. }));
601 }
602
603 #[test]
606 fn missing_name_error() {
607 let mut reg = ModuleRegistry::new();
608 reg.register(sample_module());
609
610 let err = reg
611 .resolve_symbol("app.models", "nonexistent")
612 .unwrap_err();
613 assert_eq!(
614 err,
615 RegistryError::SymbolNotFound {
616 module_id: "app.models".to_string(),
617 name: "nonexistent".to_string(),
618 }
619 );
620 }
621
622 #[test]
625 fn private_symbol_not_visible() {
626 let mut reg = ModuleRegistry::new();
627 reg.register(sample_module());
628
629 let err = reg
630 .resolve_symbol("app.models", "hash_password")
631 .unwrap_err();
632 assert_eq!(
633 err,
634 RegistryError::NotVisible {
635 module_id: "app.models".to_string(),
636 name: "hash_password".to_string(),
637 }
638 );
639 }
640
641 #[test]
644 fn get_type_returns_type_ref() {
645 let mut reg = ModuleRegistry::new();
646 reg.register(sample_module());
647
648 let ty = reg.get_type("app.models", "User").unwrap();
649 assert_eq!(ty.0, "User");
650 }
651
652 #[test]
655 fn resolve_reexport_transitively() {
656 let mut reg = ModuleRegistry::new();
657
658 let mut mod_a = ModuleExports::new("mod_a", "a.bock");
660 let (name, sym) = make_fn_symbol("greet", Visibility::Public);
661 mod_a.add_symbol(name, sym);
662 reg.register(mod_a);
663
664 let mut mod_b = ModuleExports::new("mod_b", "b.bock");
666 mod_b.add_reexport("greet", "mod_a", "greet");
667 reg.register(mod_b);
668
669 let mut mod_c = ModuleExports::new("mod_c", "c.bock");
671 mod_c.add_reexport("greet", "mod_b", "greet");
672 reg.register(mod_c);
673
674 let sym = reg.resolve_symbol("mod_c", "greet").unwrap();
676 assert_eq!(sym.kind, ExportKind::Function);
677 assert_eq!(sym.visibility, Visibility::Public);
678 }
679
680 #[test]
683 fn multiple_modules_independent() {
684 let mut reg = ModuleRegistry::new();
685
686 let mut m1 = ModuleExports::new("pkg.alpha", "alpha.bock");
687 let (name, sym) = make_fn_symbol("alpha_fn", Visibility::Public);
688 m1.add_symbol(name, sym);
689
690 let mut m2 = ModuleExports::new("pkg.beta", "beta.bock");
691 let (name, sym) = make_fn_symbol("beta_fn", Visibility::Public);
692 m2.add_symbol(name, sym);
693
694 reg.register(m1);
695 reg.register(m2);
696
697 assert_eq!(reg.module_count(), 2);
698 assert!(reg.resolve_symbol("pkg.alpha", "alpha_fn").is_ok());
699 assert!(reg.resolve_symbol("pkg.beta", "beta_fn").is_ok());
700 assert!(reg.resolve_symbol("pkg.alpha", "beta_fn").is_err());
701 assert!(reg.resolve_symbol("pkg.beta", "alpha_fn").is_err());
702 }
703
704 #[test]
707 fn register_replaces_existing() {
708 let mut reg = ModuleRegistry::new();
709
710 let mut m1 = ModuleExports::new("app.core", "core.bock");
711 let (name, sym) = make_fn_symbol("old_fn", Visibility::Public);
712 m1.add_symbol(name, sym);
713 reg.register(m1);
714
715 assert!(reg.resolve_symbol("app.core", "old_fn").is_ok());
716
717 let mut m2 = ModuleExports::new("app.core", "core.bock");
719 let (name, sym) = make_fn_symbol("new_fn", Visibility::Public);
720 m2.add_symbol(name, sym);
721 reg.register(m2);
722
723 assert!(reg.resolve_symbol("app.core", "old_fn").is_err());
724 assert!(reg.resolve_symbol("app.core", "new_fn").is_ok());
725 }
726}