1use std::collections::{HashMap, HashSet};
16
17use bock_ast::{
18 Block, EnumVariant, Expr, FnDecl, ForLoop, GuardStmt, HandlingBlock, ImplBlock, ImportItems,
19 InterpolationPart, Item, LetStmt, LoopStmt, MatchArm, Module, ModulePath, NodeId, Param,
20 Pattern, Stmt, Visibility, WhileLoop,
21};
22use bock_errors::{DiagnosticBag, DiagnosticCode, Span};
23
24use crate::registry::{ExportDetail, ExportKind, ExportedSymbol, ModuleRegistry, RegistryError};
25
26const E_UNDEFINED: DiagnosticCode = DiagnosticCode {
29 prefix: 'E',
30 number: 1001,
31};
32const E_MODULE_NOT_FOUND: DiagnosticCode = DiagnosticCode {
33 prefix: 'E',
34 number: 1005,
35};
36const E_SYMBOL_NOT_FOUND: DiagnosticCode = DiagnosticCode {
37 prefix: 'E',
38 number: 1006,
39};
40const E_NOT_VISIBLE: DiagnosticCode = DiagnosticCode {
41 prefix: 'E',
42 number: 1007,
43};
44const W_UNUSED_IMPORT: DiagnosticCode = DiagnosticCode {
45 prefix: 'W',
46 number: 1001,
47};
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum NameKind {
54 Variable,
55 Function,
56 Type,
57 Trait,
58 Effect,
59 Module,
60 Builtin,
63 Unresolved,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct ResolvedName {
71 pub def_id: NodeId,
73 pub kind: NameKind,
75}
76
77#[derive(Debug, Clone)]
79pub struct Binding {
80 pub name: String,
82 pub resolved: ResolvedName,
84 pub visibility: Visibility,
86 pub span: Span,
88 pub used: bool,
90 pub is_import: bool,
92}
93
94#[derive(Debug, Clone, Default)]
98pub struct EffectInfo {
99 pub operations: Vec<(String, NodeId, Span)>,
101 pub components: Vec<String>,
103}
104
105#[derive(Debug, Default)]
107pub struct Scope {
108 pub bindings: HashMap<String, Binding>,
110}
111
112impl Scope {
113 fn new() -> Self {
114 Self::default()
115 }
116}
117
118pub struct SymbolTable {
124 scopes: Vec<Scope>,
126 pub resolutions: HashMap<NodeId, ResolvedName>,
128 pub effect_info: HashMap<String, EffectInfo>,
132 variant_parent: HashMap<String, String>,
135}
136
137impl Default for SymbolTable {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl SymbolTable {
144 #[must_use]
146 pub fn new() -> Self {
147 Self {
148 scopes: vec![Scope::new()],
149 resolutions: HashMap::new(),
150 effect_info: HashMap::new(),
151 variant_parent: HashMap::new(),
152 }
153 }
154
155 fn seed_prelude(&mut self) {
161 const PRELUDE_BASE_ID: NodeId = u32::MAX / 4;
162
163 use crate::prelude_vocab::{
164 PRELUDE_CONSTRUCTORS, PRELUDE_FUNCTIONS as PRELUDE_FNS, PRELUDE_TRAITS, PRELUDE_TYPES,
165 };
166
167 let mut id = PRELUDE_BASE_ID;
168
169 let dummy_span = Span {
170 file: bock_errors::FileId(0),
171 start: 0,
172 end: 0,
173 };
174
175 for &name in PRELUDE_FNS {
176 self.define(
177 name.to_string(),
178 Binding {
179 name: name.to_string(),
180 resolved: ResolvedName {
181 def_id: id,
182 kind: NameKind::Builtin,
183 },
184 visibility: Visibility::Public,
185 span: dummy_span,
186 used: true, is_import: false,
188 },
189 );
190 id += 1;
191 }
192
193 for &name in PRELUDE_TYPES {
194 self.define(
195 name.to_string(),
196 Binding {
197 name: name.to_string(),
198 resolved: ResolvedName {
199 def_id: id,
200 kind: NameKind::Builtin,
201 },
202 visibility: Visibility::Public,
203 span: dummy_span,
204 used: true,
205 is_import: false,
206 },
207 );
208 id += 1;
209 }
210
211 for &name in PRELUDE_CONSTRUCTORS {
212 self.define(
213 name.to_string(),
214 Binding {
215 name: name.to_string(),
216 resolved: ResolvedName {
217 def_id: id,
218 kind: NameKind::Builtin,
219 },
220 visibility: Visibility::Public,
221 span: dummy_span,
222 used: true,
223 is_import: false,
224 },
225 );
226 id += 1;
227 }
228
229 for &name in PRELUDE_TRAITS {
230 self.define(
231 name.to_string(),
232 Binding {
233 name: name.to_string(),
234 resolved: ResolvedName {
235 def_id: id,
236 kind: NameKind::Builtin,
237 },
238 visibility: Visibility::Public,
239 span: dummy_span,
240 used: true,
241 is_import: false,
242 },
243 );
244 id += 1;
245 }
246 }
247
248 pub fn define(&mut self, name: String, binding: Binding) {
250 if let Some(scope) = self.scopes.last_mut() {
251 scope.bindings.insert(name, binding);
252 }
253 }
254
255 pub fn push_scope(&mut self) {
257 self.scopes.push(Scope::new());
258 }
259
260 pub fn pop_scope(&mut self) -> Option<Scope> {
263 if self.scopes.len() > 1 {
264 self.scopes.pop()
265 } else {
266 None
267 }
268 }
269
270 pub fn lookup(&mut self, name: &str) -> Option<ResolvedName> {
274 for scope in self.scopes.iter_mut().rev() {
275 if let Some(binding) = scope.bindings.get_mut(name) {
276 binding.used = true;
277 return Some(binding.resolved.clone());
278 }
279 }
280 None
281 }
282
283 pub fn mark_used(&mut self, name: &str) {
286 for scope in self.scopes.iter_mut().rev() {
287 if let Some(binding) = scope.bindings.get_mut(name) {
288 binding.used = true;
289 return;
290 }
291 }
292 }
293
294 #[must_use]
296 pub fn lookup_peek(&self, name: &str) -> Option<&Binding> {
297 for scope in self.scopes.iter().rev() {
298 if let Some(b) = scope.bindings.get(name) {
299 return Some(b);
300 }
301 }
302 None
303 }
304
305 pub fn record_resolution(&mut self, use_id: NodeId, resolved: ResolvedName) {
307 self.resolutions.insert(use_id, resolved);
308 }
309
310 #[must_use]
316 pub fn visible_names(&self) -> Vec<String> {
317 let mut seen: HashSet<String> = HashSet::new();
318 let mut out = Vec::new();
319 for scope in self.scopes.iter().rev() {
320 for name in scope.bindings.keys() {
321 if seen.insert(name.clone()) {
322 out.push(name.clone());
323 }
324 }
325 }
326 out
327 }
328
329 #[must_use]
331 pub fn has_wildcard_import(&self) -> bool {
332 self.scopes
333 .first()
334 .map(|s| {
335 s.bindings
336 .values()
337 .any(|b| b.is_import && b.name.ends_with(".*"))
338 })
339 .unwrap_or(false)
340 }
341
342 #[must_use]
344 pub fn unused_imports(&self) -> Vec<&Binding> {
345 self.scopes
346 .first()
347 .map(|s| {
348 s.bindings
349 .values()
350 .filter(|b| b.is_import && !b.used)
351 .collect()
352 })
353 .unwrap_or_default()
354 }
355}
356
357struct Resolver<'a> {
360 symbols: &'a mut SymbolTable,
361 diag: &'a mut DiagnosticBag,
362 synthetic_id: NodeId,
365 registry: Option<&'a ModuleRegistry>,
368}
369
370impl<'a> Resolver<'a> {
371 fn new(symbols: &'a mut SymbolTable, diag: &'a mut DiagnosticBag) -> Self {
372 Self {
373 symbols,
374 diag,
375 synthetic_id: u32::MAX / 2,
376 registry: None,
377 }
378 }
379
380 fn next_synthetic_id(&mut self) -> NodeId {
382 let id = self.synthetic_id;
383 self.synthetic_id += 1;
384 id
385 }
386
387 fn resolve_module(&mut self, module: &Module) {
390 self.symbols.seed_prelude();
393 self.collect_imports(module);
395 self.collect_items(&module.items);
397 for item in &module.items {
399 self.resolve_item(item);
400 }
401 self.check_unused_imports();
403 }
404
405 fn collect_imports(&mut self, module: &Module) {
408 for import in &module.imports {
409 let module_id = module_path_str(&import.path);
410 match &import.items {
411 ImportItems::Module => {
412 let name = import
414 .path
415 .segments
416 .last()
417 .map(|s| s.name.clone())
418 .unwrap_or_default();
419 self.symbols.define(
420 name.clone(),
421 Binding {
422 name,
423 resolved: ResolvedName {
424 def_id: import.id,
425 kind: NameKind::Module,
426 },
427 visibility: Visibility::Private,
428 span: import.span,
429 used: false,
430 is_import: true,
431 },
432 );
433 }
434 ImportItems::Named(names) => {
435 for imported in names {
436 let local = imported.alias.as_ref().unwrap_or(&imported.name);
437 let kind = if let Some(registry) = self.registry {
439 match registry.resolve_symbol(&module_id, &imported.name.name) {
440 Ok(sym) => {
441 if sym.kind == ExportKind::Effect {
444 seed_effect_info_from_registry(
445 self.symbols,
446 &local.name,
447 sym,
448 import.id,
449 import.span,
450 );
451 }
452 if sym.kind == ExportKind::Enum {
455 seed_enum_variants_from_registry(
456 self.symbols,
457 &local.name,
458 sym,
459 import.id,
460 import.span,
461 );
462 }
463 export_kind_to_name_kind(sym.kind)
464 }
465 Err(RegistryError::ModuleNotFound { .. }) => {
466 self.diag.error(
467 E_MODULE_NOT_FOUND,
468 format!("module `{module_id}` not found"),
469 import.span,
470 );
471 NameKind::Unresolved
472 }
473 Err(RegistryError::SymbolNotFound { name, .. }) => {
474 self.diag.error(
475 E_SYMBOL_NOT_FOUND,
476 format!(
477 "`{name}` is not exported by module `{module_id}`"
478 ),
479 imported.span,
480 );
481 NameKind::Unresolved
482 }
483 Err(RegistryError::NotVisible { name, .. }) => {
484 self.diag.error(
485 E_NOT_VISIBLE,
486 format!(
487 "`{name}` in module `{module_id}` is private"
488 ),
489 imported.span,
490 );
491 NameKind::Unresolved
492 }
493 }
494 } else {
495 NameKind::Unresolved
497 };
498 self.symbols.define(
499 local.name.clone(),
500 Binding {
501 name: local.name.clone(),
502 resolved: ResolvedName {
503 def_id: import.id,
504 kind,
505 },
506 visibility: Visibility::Private,
507 span: imported.span,
508 used: false,
509 is_import: true,
510 },
511 );
512 }
513 }
514 ImportItems::Glob => {
515 if let Some(registry) = self.registry {
518 match registry.resolve_glob(&module_id) {
519 Ok(exports) => {
520 for (name, sym) in exports {
521 if sym.kind == ExportKind::Effect {
523 seed_effect_info_from_registry(
524 self.symbols,
525 name,
526 sym,
527 import.id,
528 import.span,
529 );
530 }
531 if sym.kind == ExportKind::Enum {
534 seed_enum_variants_from_registry(
535 self.symbols,
536 name,
537 sym,
538 import.id,
539 import.span,
540 );
541 }
542 self.symbols.define(
543 name.to_string(),
544 Binding {
545 name: name.to_string(),
546 resolved: ResolvedName {
547 def_id: import.id,
548 kind: export_kind_to_name_kind(sym.kind),
549 },
550 visibility: Visibility::Private,
551 span: import.span,
552 used: false,
553 is_import: true,
554 },
555 );
556 }
557 }
558 Err(RegistryError::ModuleNotFound { .. }) => {
559 self.diag.error(
560 E_MODULE_NOT_FOUND,
561 format!("module `{module_id}` not found"),
562 import.span,
563 );
564 }
565 Err(e) => {
566 self.diag.error(
567 E_SYMBOL_NOT_FOUND,
568 format!("{e}"),
569 import.span,
570 );
571 }
572 }
573 }
574 let sentinel = format!("{}.*", module_id);
577 self.symbols.define(
578 sentinel.clone(),
579 Binding {
580 name: sentinel,
581 resolved: ResolvedName {
582 def_id: import.id,
583 kind: NameKind::Module,
584 },
585 visibility: Visibility::Private,
586 span: import.span,
587 used: true, is_import: true,
589 },
590 );
591 }
592 }
593 }
594 }
595
596 fn collect_items(&mut self, items: &[Item]) {
597 for item in items {
598 match item {
599 Item::Fn(d) => {
600 self.symbols.define(
601 d.name.name.clone(),
602 Binding {
603 name: d.name.name.clone(),
604 resolved: ResolvedName {
605 def_id: d.id,
606 kind: NameKind::Function,
607 },
608 visibility: d.visibility,
609 span: d.span,
610 used: false,
611 is_import: false,
612 },
613 );
614 }
615 Item::Record(d) => {
616 self.symbols.define(
617 d.name.name.clone(),
618 Binding {
619 name: d.name.name.clone(),
620 resolved: ResolvedName {
621 def_id: d.id,
622 kind: NameKind::Type,
623 },
624 visibility: d.visibility,
625 span: d.span,
626 used: false,
627 is_import: false,
628 },
629 );
630 }
631 Item::Enum(d) => {
632 self.symbols.define(
633 d.name.name.clone(),
634 Binding {
635 name: d.name.name.clone(),
636 resolved: ResolvedName {
637 def_id: d.id,
638 kind: NameKind::Type,
639 },
640 visibility: d.visibility,
641 span: d.span,
642 used: false,
643 is_import: false,
644 },
645 );
646 for variant in &d.variants {
648 let (vname, vid, vspan) = match variant {
649 EnumVariant::Unit { name, id, span } => (name, id, span),
650 EnumVariant::Struct { name, id, span, .. } => (name, id, span),
651 EnumVariant::Tuple { name, id, span, .. } => (name, id, span),
652 };
653 self.symbols.define(
654 vname.name.clone(),
655 Binding {
656 name: vname.name.clone(),
657 resolved: ResolvedName {
658 def_id: *vid,
659 kind: NameKind::Function,
660 },
661 visibility: d.visibility,
662 span: *vspan,
663 used: false,
664 is_import: false,
665 },
666 );
667 }
668 }
669 Item::Class(d) => {
670 self.symbols.define(
671 d.name.name.clone(),
672 Binding {
673 name: d.name.name.clone(),
674 resolved: ResolvedName {
675 def_id: d.id,
676 kind: NameKind::Type,
677 },
678 visibility: d.visibility,
679 span: d.span,
680 used: false,
681 is_import: false,
682 },
683 );
684 }
685 Item::Trait(d) | Item::PlatformTrait(d) => {
686 self.symbols.define(
687 d.name.name.clone(),
688 Binding {
689 name: d.name.name.clone(),
690 resolved: ResolvedName {
691 def_id: d.id,
692 kind: NameKind::Trait,
693 },
694 visibility: d.visibility,
695 span: d.span,
696 used: false,
697 is_import: false,
698 },
699 );
700 }
701 Item::Effect(d) => {
702 self.symbols.define(
703 d.name.name.clone(),
704 Binding {
705 name: d.name.name.clone(),
706 resolved: ResolvedName {
707 def_id: d.id,
708 kind: NameKind::Effect,
709 },
710 visibility: d.visibility,
711 span: d.span,
712 used: false,
713 is_import: false,
714 },
715 );
716 let ops: Vec<(String, NodeId, Span)> = d
719 .operations
720 .iter()
721 .map(|op| (op.name.name.clone(), op.id, op.span))
722 .collect();
723 let components: Vec<String> = d
724 .components
725 .iter()
726 .map(|tp| {
727 tp.segments
728 .iter()
729 .map(|s| s.name.as_str())
730 .collect::<Vec<_>>()
731 .join(".")
732 })
733 .collect();
734 self.symbols.effect_info.insert(
735 d.name.name.clone(),
736 EffectInfo {
737 operations: ops,
738 components,
739 },
740 );
741 }
742 Item::TypeAlias(d) => {
743 self.symbols.define(
744 d.name.name.clone(),
745 Binding {
746 name: d.name.name.clone(),
747 resolved: ResolvedName {
748 def_id: d.id,
749 kind: NameKind::Type,
750 },
751 visibility: d.visibility,
752 span: d.span,
753 used: false,
754 is_import: false,
755 },
756 );
757 }
758 Item::Const(d) => {
759 self.symbols.define(
760 d.name.name.clone(),
761 Binding {
762 name: d.name.name.clone(),
763 resolved: ResolvedName {
764 def_id: d.id,
765 kind: NameKind::Variable,
766 },
767 visibility: d.visibility,
768 span: d.span,
769 used: false,
770 is_import: false,
771 },
772 );
773 }
774 Item::Impl(_)
777 | Item::ModuleHandle(_)
778 | Item::PropertyTest(_)
779 | Item::Error { .. } => {}
780 }
781 }
782 }
783
784 fn resolve_item(&mut self, item: &Item) {
787 match item {
788 Item::Fn(d) => self.resolve_fn(d),
789 Item::Impl(d) => self.resolve_impl(d),
790 Item::Class(d) => {
791 for m in &d.methods {
792 self.resolve_fn(m);
793 }
794 }
795 Item::Trait(d) | Item::PlatformTrait(d) => {
796 for m in &d.methods {
797 self.resolve_fn(m);
798 }
799 }
800 Item::Effect(d) => {
801 for op in &d.operations {
802 self.resolve_fn(op);
803 }
804 }
805 Item::Const(d) => self.resolve_expr(&d.value),
806 Item::ModuleHandle(d) => self.resolve_expr(&d.handler),
807 Item::PropertyTest(d) => self.resolve_block(&d.body),
808 Item::Record(_) | Item::Enum(_) | Item::TypeAlias(_) | Item::Error { .. } => {}
810 }
811 }
812
813 fn resolve_fn(&mut self, d: &FnDecl) {
814 self.symbols.push_scope();
815 for param in &d.params {
816 self.resolve_param(param);
817 }
818 if let Some(ret) = &d.return_type {
819 self.resolve_type_expr(ret);
820 }
821 self.inject_effect_operations(&d.effect_clause);
824 if let Some(ref body) = d.body {
825 self.resolve_block_body(body);
826 }
827 self.symbols.pop_scope();
828 }
829
830 fn inject_effect_operations(&mut self, effect_clause: &[bock_ast::TypePath]) {
833 let mut visited = HashSet::new();
834 for effect_path in effect_clause {
835 let effect_name = effect_path
836 .segments
837 .iter()
838 .map(|s| s.name.as_str())
839 .collect::<Vec<_>>()
840 .join(".");
841 self.inject_ops_for_effect(&effect_name, &mut visited);
842 }
843 }
844
845 fn inject_ops_for_effect(&mut self, effect_name: &str, visited: &mut HashSet<String>) {
847 if !visited.insert(effect_name.to_string()) {
848 return; }
850 let info = self.symbols.effect_info.get(effect_name).cloned();
852 if let Some(info) = info {
853 for (op_name, op_id, op_span) in &info.operations {
854 self.symbols.define(
855 op_name.clone(),
856 Binding {
857 name: op_name.clone(),
858 resolved: ResolvedName {
859 def_id: *op_id,
860 kind: NameKind::Function,
861 },
862 visibility: Visibility::Public,
863 span: *op_span,
864 used: true, is_import: false,
866 },
867 );
868 }
869 let components = info.components.clone();
871 for component in &components {
872 self.inject_ops_for_effect(component, visited);
873 }
874 }
875 }
876
877 fn resolve_param(&mut self, param: &Param) {
878 self.collect_pattern_bindings(¶m.pattern, NameKind::Variable, Visibility::Private);
879 if let Some(ty) = ¶m.ty {
880 self.resolve_type_expr(ty);
881 }
882 if let Some(default) = ¶m.default {
883 self.resolve_expr(default);
884 }
885 }
886
887 fn resolve_impl(&mut self, d: &ImplBlock) {
888 for m in &d.methods {
889 let has_self = m.params.iter().any(|p| {
891 matches!(&p.pattern, Pattern::Bind { name, .. } if name.name == "self")
892 });
893
894 if has_self || d.trait_path.is_none() {
895 self.resolve_fn(m);
897 } else {
898 self.symbols.push_scope();
902 let syn_id = self.next_synthetic_id();
903 self.symbols.define(
904 "self".to_string(),
905 Binding {
906 name: "self".to_string(),
907 resolved: ResolvedName {
908 def_id: syn_id,
909 kind: NameKind::Variable,
910 },
911 visibility: Visibility::Private,
912 span: m.span,
913 used: false,
914 is_import: false,
915 },
916 );
917 for param in &m.params {
918 self.resolve_param(param);
919 }
920 self.inject_effect_operations(&m.effect_clause);
921 if let Some(ref body) = m.body {
922 self.resolve_block_body(body);
923 }
924 self.symbols.pop_scope();
925 }
926 }
927 }
928
929 fn resolve_block(&mut self, block: &Block) {
933 self.symbols.push_scope();
934 self.resolve_block_body(block);
935 self.symbols.pop_scope();
936 }
937
938 fn resolve_block_body(&mut self, block: &Block) {
943 for stmt in &block.stmts {
944 self.resolve_stmt(stmt);
945 }
946 if let Some(tail) = &block.tail {
947 self.resolve_expr(tail);
948 }
949 }
950
951 fn resolve_stmt(&mut self, stmt: &Stmt) {
952 match stmt {
953 Stmt::Let(s) => self.resolve_let(s),
954 Stmt::Expr(e) => self.resolve_expr(e),
955 Stmt::For(f) => self.resolve_for(f),
956 Stmt::While(w) => self.resolve_while(w),
957 Stmt::Loop(l) => self.resolve_loop(l),
958 Stmt::Guard(g) => self.resolve_guard(g),
959 Stmt::Handling(h) => self.resolve_handling(h),
960 Stmt::Empty => {}
961 }
962 }
963
964 fn resolve_let(&mut self, s: &LetStmt) {
965 self.resolve_expr(&s.value);
967 if let Some(ty) = &s.ty {
968 self.resolve_type_expr(ty);
969 }
970 self.collect_pattern_bindings(&s.pattern, NameKind::Variable, Visibility::Private);
971 }
972
973 fn resolve_for(&mut self, f: &ForLoop) {
974 self.resolve_expr(&f.iterable);
975 self.symbols.push_scope();
977 self.collect_pattern_bindings(&f.pattern, NameKind::Variable, Visibility::Private);
978 self.resolve_block_body(&f.body);
979 self.symbols.pop_scope();
980 }
981
982 fn resolve_while(&mut self, w: &WhileLoop) {
983 self.resolve_expr(&w.condition);
984 self.resolve_block(&w.body);
985 }
986
987 fn resolve_loop(&mut self, l: &LoopStmt) {
988 self.resolve_block(&l.body);
989 }
990
991 fn resolve_guard(&mut self, g: &GuardStmt) {
992 self.resolve_expr(&g.condition);
993 if let Some(pat) = &g.let_pattern {
994 self.collect_pattern_bindings(pat, NameKind::Variable, Visibility::Private);
995 }
996 self.resolve_block(&g.else_block);
997 }
998
999 fn resolve_handling(&mut self, h: &HandlingBlock) {
1000 for pair in &h.handlers {
1001 self.resolve_expr(&pair.handler);
1002 }
1003 self.resolve_block(&h.body);
1004 }
1005
1006 fn resolve_expr(&mut self, expr: &Expr) {
1009 match expr {
1010 Expr::Identifier { id, name, .. } => {
1011 if let Some(resolved) = self.symbols.lookup(&name.name) {
1012 self.symbols.record_resolution(*id, resolved);
1013 } else if !self.symbols.has_wildcard_import() {
1014 let visible = self.symbols.visible_names();
1015 let diag = self.diag.error(
1016 E_UNDEFINED,
1017 format!("undefined name `{}`", name.name),
1018 name.span,
1019 );
1020 if let Some(hint) = keyword_hint(&name.name) {
1021 diag.note(hint);
1022 } else if let Some(suggestion) =
1023 bock_errors::suggest_similar(&name.name, visible, 2)
1024 {
1025 diag.note(format!("did you mean `{suggestion}`?"));
1026 }
1027 }
1028 }
1029
1030 Expr::Literal { .. }
1032 | Expr::Continue { .. }
1033 | Expr::Unreachable { .. }
1034 | Expr::Placeholder { .. } => {}
1035
1036 Expr::Binary { left, right, .. } => {
1037 self.resolve_expr(left);
1038 self.resolve_expr(right);
1039 }
1040 Expr::Unary { operand, .. } => self.resolve_expr(operand),
1041 Expr::Assign { target, value, .. } => {
1042 self.resolve_expr(target);
1043 self.resolve_expr(value);
1044 }
1045 Expr::Call { callee, args, .. } => {
1046 self.resolve_expr(callee);
1047 for arg in args {
1048 self.resolve_expr(&arg.value);
1049 }
1050 }
1051 Expr::MethodCall { receiver, args, .. } => {
1052 self.resolve_expr(receiver);
1053 for arg in args {
1054 self.resolve_expr(&arg.value);
1055 }
1056 }
1057 Expr::FieldAccess { object, .. } => self.resolve_expr(object),
1058 Expr::Index { object, index, .. } => {
1059 self.resolve_expr(object);
1060 self.resolve_expr(index);
1061 }
1062 Expr::Try { expr, .. } => self.resolve_expr(expr),
1063 Expr::Lambda { params, body, .. } => {
1064 self.symbols.push_scope();
1065 for p in params {
1066 self.resolve_param(p);
1067 }
1068 self.resolve_expr(body);
1069 self.symbols.pop_scope();
1070 }
1071 Expr::Pipe { left, right, .. } | Expr::Compose { left, right, .. } => {
1072 self.resolve_expr(left);
1073 self.resolve_expr(right);
1074 }
1075 Expr::If {
1076 condition,
1077 let_pattern,
1078 then_block,
1079 else_block,
1080 ..
1081 } => {
1082 self.resolve_expr(condition);
1083 self.symbols.push_scope();
1086 if let Some(pat) = let_pattern {
1087 self.collect_pattern_bindings(pat, NameKind::Variable, Visibility::Private);
1088 }
1089 self.resolve_block_body(then_block);
1090 self.symbols.pop_scope();
1091 if let Some(eb) = else_block {
1092 self.resolve_expr(eb);
1093 }
1094 }
1095 Expr::Match {
1096 scrutinee, arms, ..
1097 } => {
1098 self.resolve_expr(scrutinee);
1099 for arm in arms {
1100 self.resolve_match_arm(arm);
1101 }
1102 }
1103 Expr::Loop { body, .. } => self.resolve_block(body),
1104 Expr::Block { block, .. } => self.resolve_block(block),
1105 Expr::RecordConstruct {
1106 path,
1107 fields,
1108 spread,
1109 ..
1110 } => {
1111 if let Some(first) = path.segments.first() {
1112 self.symbols.mark_used(&first.name);
1113 }
1114 for f in fields {
1115 if let Some(v) = &f.value {
1116 self.resolve_expr(v);
1117 }
1118 }
1119 if let Some(s) = spread {
1120 self.resolve_expr(&s.expr);
1121 }
1122 }
1123 Expr::ListLiteral { elems, .. }
1124 | Expr::SetLiteral { elems, .. }
1125 | Expr::TupleLiteral { elems, .. } => {
1126 for e in elems {
1127 self.resolve_expr(e);
1128 }
1129 }
1130 Expr::MapLiteral { entries, .. } => {
1131 for (k, v) in entries {
1132 self.resolve_expr(k);
1133 self.resolve_expr(v);
1134 }
1135 }
1136 Expr::Range { lo, hi, .. } => {
1137 self.resolve_expr(lo);
1138 self.resolve_expr(hi);
1139 }
1140 Expr::Await { expr, .. } => self.resolve_expr(expr),
1141 Expr::Return { value, .. } | Expr::Break { value, .. } => {
1142 if let Some(v) = value {
1143 self.resolve_expr(v);
1144 }
1145 }
1146 Expr::Interpolation { parts, .. } => {
1147 for part in parts {
1148 if let InterpolationPart::Expr(e) = part {
1149 self.resolve_expr(e);
1150 }
1151 }
1152 }
1153 Expr::Is { expr, .. } => {
1154 self.resolve_expr(expr);
1155 }
1156 }
1157 }
1158
1159 fn resolve_type_expr(&mut self, ty: &bock_ast::TypeExpr) {
1161 match ty {
1162 bock_ast::TypeExpr::Named { path, args, .. } => {
1163 if let Some(first) = path.segments.first() {
1164 self.symbols.mark_used(&first.name);
1165 }
1166 for arg in args {
1167 self.resolve_type_expr(arg);
1168 }
1169 }
1170 bock_ast::TypeExpr::Tuple { elems, .. } => {
1171 for e in elems {
1172 self.resolve_type_expr(e);
1173 }
1174 }
1175 bock_ast::TypeExpr::Function {
1176 params, ret, effects, ..
1177 } => {
1178 for p in params {
1179 self.resolve_type_expr(p);
1180 }
1181 self.resolve_type_expr(ret);
1182 for eff in effects {
1183 if let Some(first) = eff.segments.first() {
1184 self.symbols.mark_used(&first.name);
1185 }
1186 }
1187 }
1188 bock_ast::TypeExpr::Optional { inner, .. } => {
1189 self.resolve_type_expr(inner);
1190 }
1191 bock_ast::TypeExpr::SelfType { .. } => {}
1192 }
1193 }
1194
1195 fn resolve_match_arm(&mut self, arm: &MatchArm) {
1196 self.symbols.push_scope();
1197 self.collect_pattern_bindings(&arm.pattern, NameKind::Variable, Visibility::Private);
1198 if let Some(g) = &arm.guard {
1199 self.resolve_expr(g);
1200 }
1201 self.resolve_expr(&arm.body);
1202 self.symbols.pop_scope();
1203 }
1204
1205 fn collect_pattern_bindings(
1208 &mut self,
1209 pattern: &Pattern,
1210 kind: NameKind,
1211 visibility: Visibility,
1212 ) {
1213 match pattern {
1214 Pattern::Wildcard { .. } | Pattern::Literal { .. } | Pattern::Rest { .. } => {}
1216
1217 Pattern::Bind { id, span, name } | Pattern::MutBind { id, span, name } => {
1218 self.symbols.define(
1219 name.name.clone(),
1220 Binding {
1221 name: name.name.clone(),
1222 resolved: ResolvedName { def_id: *id, kind },
1223 visibility,
1224 span: *span,
1225 used: false,
1226 is_import: false,
1227 },
1228 );
1229 }
1230
1231 Pattern::Constructor { path, fields, .. } => {
1232 if let Some(first) = path.segments.first() {
1233 self.symbols.mark_used(&first.name);
1234 }
1235 for f in fields {
1236 self.collect_pattern_bindings(f, kind, visibility);
1237 }
1238 }
1239 Pattern::Tuple { elems, .. } => {
1240 for e in elems {
1241 self.collect_pattern_bindings(e, kind, visibility);
1242 }
1243 }
1244 Pattern::Record { path, fields, .. } => {
1245 if let Some(first) = path.segments.first() {
1246 self.symbols.mark_used(&first.name);
1247 }
1248 for f in fields {
1249 if let Some(p) = &f.pattern {
1250 self.collect_pattern_bindings(p, kind, visibility);
1251 } else {
1252 let syn_id = self.next_synthetic_id();
1254 self.symbols.define(
1255 f.name.name.clone(),
1256 Binding {
1257 name: f.name.name.clone(),
1258 resolved: ResolvedName {
1259 def_id: syn_id,
1260 kind,
1261 },
1262 visibility,
1263 span: f.span,
1264 used: false,
1265 is_import: false,
1266 },
1267 );
1268 }
1269 }
1270 }
1271 Pattern::List { elems, rest, .. } => {
1272 for e in elems {
1273 self.collect_pattern_bindings(e, kind, visibility);
1274 }
1275 if let Some(r) = rest {
1276 self.collect_pattern_bindings(r, kind, visibility);
1277 }
1278 }
1279 Pattern::Or { alternatives, .. } => {
1280 if let Some(first) = alternatives.first() {
1283 self.collect_pattern_bindings(first, kind, visibility);
1284 }
1285 }
1286 Pattern::Range { lo, hi, .. } => {
1287 self.collect_pattern_bindings(lo, kind, visibility);
1288 self.collect_pattern_bindings(hi, kind, visibility);
1289 }
1290 }
1291 }
1292
1293 fn check_unused_imports(&mut self) {
1296 if let Some(scope) = self.symbols.scopes.first() {
1299 let used_parents: Vec<String> = self
1300 .symbols
1301 .variant_parent
1302 .iter()
1303 .filter(|(variant, _)| {
1304 scope
1305 .bindings
1306 .get(variant.as_str())
1307 .is_some_and(|b| b.used)
1308 })
1309 .map(|(_, parent)| parent.clone())
1310 .collect();
1311 for parent in used_parents {
1312 self.symbols.mark_used(&parent);
1313 }
1314 }
1315
1316 let unused: Vec<(String, Span)> = self
1319 .symbols
1320 .scopes
1321 .first()
1322 .map(|s| {
1323 s.bindings
1324 .values()
1325 .filter(|b| b.is_import && !b.used)
1326 .map(|b| (b.name.clone(), b.span))
1327 .collect()
1328 })
1329 .unwrap_or_default();
1330
1331 for (name, span) in unused {
1332 self.diag
1333 .warning(W_UNUSED_IMPORT, format!("unused import `{name}`"), span);
1334 }
1335 }
1336}
1337
1338fn module_path_str(path: &ModulePath) -> String {
1341 path.segments
1342 .iter()
1343 .map(|s| s.name.as_str())
1344 .collect::<Vec<_>>()
1345 .join(".")
1346}
1347
1348fn export_kind_to_name_kind(kind: ExportKind) -> NameKind {
1350 match kind {
1351 ExportKind::Function => NameKind::Function,
1352 ExportKind::Record | ExportKind::Enum | ExportKind::TypeAlias => NameKind::Type,
1353 ExportKind::Trait => NameKind::Trait,
1354 ExportKind::Effect => NameKind::Effect,
1355 ExportKind::Constant => NameKind::Variable,
1356 }
1357}
1358
1359fn seed_effect_info_from_registry(
1362 symbols: &mut SymbolTable,
1363 local_name: &str,
1364 sym: &ExportedSymbol,
1365 import_id: NodeId,
1366 import_span: Span,
1367) {
1368 if let ExportDetail::Effect {
1369 operations,
1370 components,
1371 } = &sym.detail
1372 {
1373 let ops: Vec<(String, NodeId, Span)> = operations
1374 .iter()
1375 .map(|(name, _type_ref)| (name.clone(), import_id, import_span))
1376 .collect();
1377 symbols.effect_info.insert(
1378 local_name.to_string(),
1379 EffectInfo {
1380 operations: ops,
1381 components: components.clone(),
1382 },
1383 );
1384 }
1385}
1386
1387fn seed_enum_variants_from_registry(
1392 symbols: &mut SymbolTable,
1393 enum_name: &str,
1394 sym: &ExportedSymbol,
1395 import_id: NodeId,
1396 import_span: Span,
1397) {
1398 if let ExportDetail::Enum { variants, .. } = &sym.detail {
1399 for variant in variants {
1400 symbols.variant_parent.insert(
1401 variant.name.clone(),
1402 enum_name.to_string(),
1403 );
1404 symbols.define(
1405 variant.name.clone(),
1406 Binding {
1407 name: variant.name.clone(),
1408 resolved: ResolvedName {
1409 def_id: import_id,
1410 kind: NameKind::Function,
1411 },
1412 visibility: Visibility::Private,
1413 span: import_span,
1414 used: false,
1415 is_import: false,
1418 },
1419 );
1420 }
1421 }
1422}
1423
1424fn keyword_hint(name: &str) -> Option<&'static str> {
1433 match name {
1434 "pub" => Some("Bock uses `public` for visibility, not `pub`"),
1435 "var" => Some("Bock uses `let mut` for mutable bindings, not `var`"),
1436 "func" | "def" => Some("Bock uses `fn` to declare functions"),
1437 "interface" => Some("Bock uses `trait` for interfaces"),
1438 "struct" => Some("Bock uses `record` for value types"),
1439 "class" => Some("Bock uses `record` for data and `trait` for behavior — there is no `class`"),
1440 "None_" | "nil" | "null" | "undefined" => {
1441 Some("Bock uses `None` (from `Optional[T]`) to represent absent values")
1442 }
1443 "true_" | "false_" => Some("Bock boolean literals are `true` and `false`"),
1444 _ => None,
1445 }
1446}
1447
1448pub fn resolve_names(ast: &Module, symbols: &mut SymbolTable) -> DiagnosticBag {
1460 let mut diag = DiagnosticBag::new();
1461 let mut resolver = Resolver::new(symbols, &mut diag);
1462 resolver.resolve_module(ast);
1463 diag
1464}
1465
1466pub fn resolve_names_with_registry(
1475 ast: &Module,
1476 symbols: &mut SymbolTable,
1477 registry: &ModuleRegistry,
1478) -> DiagnosticBag {
1479 let mut diag = DiagnosticBag::new();
1480 let mut resolver = Resolver::new(symbols, &mut diag);
1481 resolver.registry = Some(registry);
1482 resolver.resolve_module(ast);
1483 diag
1484}
1485
1486#[cfg(test)]
1489mod tests {
1490 use super::*;
1491 use bock_ast::{
1492 Block, FnDecl, Ident, ImportDecl, ImportItems, ImportedName, Item, Literal, Module,
1493 ModulePath, Param, Pattern, Stmt, Visibility,
1494 };
1495 use bock_errors::{FileId, Span};
1496
1497 fn sp() -> Span {
1498 Span {
1499 file: FileId(0),
1500 start: 0,
1501 end: 1,
1502 }
1503 }
1504
1505 fn ident(name: &str) -> Ident {
1506 Ident {
1507 name: name.to_string(),
1508 span: sp(),
1509 }
1510 }
1511
1512 fn mpath(segments: &[&str]) -> ModulePath {
1513 ModulePath {
1514 segments: segments.iter().map(|s| ident(s)).collect(),
1515 span: sp(),
1516 }
1517 }
1518
1519 fn empty_block(id: NodeId) -> Block {
1520 Block {
1521 id,
1522 span: sp(),
1523 stmts: vec![],
1524 tail: None,
1525 }
1526 }
1527
1528 fn simple_module(imports: Vec<ImportDecl>, items: Vec<Item>) -> Module {
1529 Module {
1530 id: 0,
1531 span: sp(),
1532 doc: vec![],
1533 path: None,
1534 imports,
1535 items,
1536 }
1537 }
1538
1539 fn fn_item(id: NodeId, name: &str, vis: Visibility) -> Item {
1540 Item::Fn(FnDecl {
1541 id,
1542 span: sp(),
1543 annotations: vec![],
1544 visibility: vis,
1545 is_async: false,
1546 name: ident(name),
1547 generic_params: vec![],
1548 params: vec![],
1549 return_type: None,
1550 effect_clause: vec![],
1551 where_clause: vec![],
1552 body: Some(empty_block(id + 100)),
1553 })
1554 }
1555
1556 #[test]
1559 fn symbol_table_define_and_lookup() {
1560 let mut st = SymbolTable::new();
1561 st.define(
1562 "foo".into(),
1563 Binding {
1564 name: "foo".into(),
1565 resolved: ResolvedName {
1566 def_id: 1,
1567 kind: NameKind::Function,
1568 },
1569 visibility: Visibility::Public,
1570 span: sp(),
1571 used: false,
1572 is_import: false,
1573 },
1574 );
1575 let r = st.lookup("foo").unwrap();
1576 assert_eq!(r.def_id, 1);
1577 assert_eq!(r.kind, NameKind::Function);
1578 }
1579
1580 #[test]
1581 fn symbol_table_lookup_marks_used() {
1582 let mut st = SymbolTable::new();
1583 st.define(
1584 "x".into(),
1585 Binding {
1586 name: "x".into(),
1587 resolved: ResolvedName {
1588 def_id: 5,
1589 kind: NameKind::Variable,
1590 },
1591 visibility: Visibility::Private,
1592 span: sp(),
1593 used: false,
1594 is_import: false,
1595 },
1596 );
1597 st.lookup("x");
1598 assert!(st.lookup_peek("x").unwrap().used);
1599 }
1600
1601 #[test]
1602 fn symbol_table_inner_scope_shadows_outer() {
1603 let mut st = SymbolTable::new();
1604 st.define(
1605 "x".into(),
1606 Binding {
1607 name: "x".into(),
1608 resolved: ResolvedName {
1609 def_id: 1,
1610 kind: NameKind::Variable,
1611 },
1612 visibility: Visibility::Private,
1613 span: sp(),
1614 used: false,
1615 is_import: false,
1616 },
1617 );
1618 st.push_scope();
1619 st.define(
1620 "x".into(),
1621 Binding {
1622 name: "x".into(),
1623 resolved: ResolvedName {
1624 def_id: 2,
1625 kind: NameKind::Variable,
1626 },
1627 visibility: Visibility::Private,
1628 span: sp(),
1629 used: false,
1630 is_import: false,
1631 },
1632 );
1633 assert_eq!(st.lookup("x").unwrap().def_id, 2);
1635 st.pop_scope();
1636 assert_eq!(st.lookup("x").unwrap().def_id, 1);
1638 }
1639
1640 #[test]
1641 fn symbol_table_lookup_unknown_returns_none() {
1642 let mut st = SymbolTable::new();
1643 assert!(st.lookup("unknown").is_none());
1644 }
1645
1646 #[test]
1647 fn symbol_table_module_scope_never_popped() {
1648 let mut st = SymbolTable::new();
1649 assert!(st.pop_scope().is_none()); }
1651
1652 #[test]
1655 fn resolve_defined_identifier() {
1656 let module = simple_module(
1659 vec![],
1660 vec![
1661 fn_item(1, "foo", Visibility::Private),
1662 Item::Fn(FnDecl {
1663 id: 2,
1664 span: sp(),
1665 annotations: vec![],
1666 visibility: Visibility::Private,
1667 is_async: false,
1668 name: ident("bar"),
1669 generic_params: vec![],
1670 params: vec![],
1671 return_type: None,
1672 effect_clause: vec![],
1673 where_clause: vec![],
1674 body: Some(Block {
1675 id: 200,
1676 span: sp(),
1677 stmts: vec![],
1678 tail: Some(Box::new(Expr::Identifier {
1679 id: 99,
1680 span: sp(),
1681 name: ident("foo"),
1682 })),
1683 }),
1684 }),
1685 ],
1686 );
1687 let mut st = SymbolTable::new();
1688 let diag = resolve_names(&module, &mut st);
1689 assert!(
1690 !diag.has_errors(),
1691 "unexpected errors: {:?}",
1692 diag.iter().collect::<Vec<_>>()
1693 );
1694 let resolved = st
1695 .resolutions
1696 .get(&99)
1697 .expect("identifier should be resolved");
1698 assert_eq!(resolved.def_id, 1);
1699 assert_eq!(resolved.kind, NameKind::Function);
1700 }
1701
1702 #[test]
1703 fn resolve_undefined_identifier_produces_error() {
1704 let module = simple_module(
1705 vec![],
1706 vec![Item::Fn(FnDecl {
1707 id: 1,
1708 span: sp(),
1709 annotations: vec![],
1710 visibility: Visibility::Private,
1711 is_async: false,
1712 name: ident("bar"),
1713 generic_params: vec![],
1714 params: vec![],
1715 return_type: None,
1716 effect_clause: vec![],
1717 where_clause: vec![],
1718 body: Some(Block {
1719 id: 100,
1720 span: sp(),
1721 stmts: vec![],
1722 tail: Some(Box::new(Expr::Identifier {
1723 id: 42,
1724 span: sp(),
1725 name: ident("undefined_thing"),
1726 })),
1727 }),
1728 })],
1729 );
1730 let mut st = SymbolTable::new();
1731 let diag = resolve_names(&module, &mut st);
1732 assert!(diag.has_errors());
1733 let msgs: Vec<_> = diag.iter().map(|d| d.message.as_str()).collect();
1734 assert!(msgs.iter().any(|m| m.contains("undefined_thing")));
1735 }
1736
1737 #[test]
1740 fn named_import_creates_binding() {
1741 let import = ImportDecl {
1742 id: 10,
1743 span: sp(),
1744 visibility: Visibility::Private,
1745 path: mpath(&["core", "collections"]),
1746 items: ImportItems::Named(vec![
1747 ImportedName {
1748 span: sp(),
1749 name: ident("List"),
1750 alias: None,
1751 },
1752 ImportedName {
1753 span: sp(),
1754 name: ident("Map"),
1755 alias: None,
1756 },
1757 ]),
1758 };
1759 let module = simple_module(vec![import], vec![]);
1760 let mut st = SymbolTable::new();
1761 resolve_names(&module, &mut st);
1762 assert!(st.lookup_peek("List").is_some());
1764 assert!(st.lookup_peek("Map").is_some());
1765 }
1766
1767 #[test]
1768 fn named_import_with_alias() {
1769 let import = ImportDecl {
1770 id: 10,
1771 span: sp(),
1772 visibility: Visibility::Private,
1773 path: mpath(&["core"]),
1774 items: ImportItems::Named(vec![ImportedName {
1775 span: sp(),
1776 name: ident("FooBar"),
1777 alias: Some(ident("FB")),
1778 }]),
1779 };
1780 let module = simple_module(vec![import], vec![]);
1781 let mut st = SymbolTable::new();
1782 resolve_names(&module, &mut st);
1783 assert!(st.lookup_peek("FB").is_some());
1785 assert!(st.lookup_peek("FooBar").is_none());
1786 }
1787
1788 #[test]
1789 fn module_import_creates_binding() {
1790 let import = ImportDecl {
1791 id: 10,
1792 span: sp(),
1793 visibility: Visibility::Private,
1794 path: mpath(&["app", "models"]),
1795 items: ImportItems::Module,
1796 };
1797 let module = simple_module(vec![import], vec![]);
1798 let mut st = SymbolTable::new();
1799 resolve_names(&module, &mut st);
1800 let b = st.lookup_peek("models").expect("models should be bound");
1802 assert_eq!(b.resolved.kind, NameKind::Module);
1803 }
1804
1805 #[test]
1806 fn wildcard_import_suppresses_undefined_errors() {
1807 let import = ImportDecl {
1810 id: 10,
1811 span: sp(),
1812 visibility: Visibility::Private,
1813 path: mpath(&["some", "module"]),
1814 items: ImportItems::Glob,
1815 };
1816 let module = simple_module(
1817 vec![import],
1818 vec![Item::Fn(FnDecl {
1819 id: 1,
1820 span: sp(),
1821 annotations: vec![],
1822 visibility: Visibility::Private,
1823 is_async: false,
1824 name: ident("test"),
1825 generic_params: vec![],
1826 params: vec![],
1827 return_type: None,
1828 effect_clause: vec![],
1829 where_clause: vec![],
1830 body: Some(Block {
1831 id: 100,
1832 span: sp(),
1833 stmts: vec![],
1834 tail: Some(Box::new(Expr::Identifier {
1835 id: 99,
1836 span: sp(),
1837 name: ident("SomethingFromWildcard"),
1838 })),
1839 }),
1840 })],
1841 );
1842 let mut st = SymbolTable::new();
1843 let diag = resolve_names(&module, &mut st);
1844 assert!(
1845 !diag.has_errors(),
1846 "wildcard import should suppress undefined errors"
1847 );
1848 }
1849
1850 #[test]
1853 fn unused_named_import_produces_warning() {
1854 let import = ImportDecl {
1855 id: 10,
1856 span: sp(),
1857 visibility: Visibility::Private,
1858 path: mpath(&["core"]),
1859 items: ImportItems::Named(vec![ImportedName {
1860 span: sp(),
1861 name: ident("Unused"),
1862 alias: None,
1863 }]),
1864 };
1865 let module = simple_module(vec![import], vec![]);
1866 let mut st = SymbolTable::new();
1867 let diag = resolve_names(&module, &mut st);
1868 assert!(!diag.has_errors());
1869 let warnings: Vec<_> = diag
1870 .iter()
1871 .filter(|d| d.severity == bock_errors::Severity::Warning)
1872 .collect();
1873 assert!(!warnings.is_empty(), "expected unused import warning");
1874 assert!(warnings.iter().any(|w| w.message.contains("Unused")));
1875 }
1876
1877 #[test]
1878 fn used_import_no_warning() {
1879 let import = ImportDecl {
1880 id: 10,
1881 span: sp(),
1882 visibility: Visibility::Private,
1883 path: mpath(&["core"]),
1884 items: ImportItems::Named(vec![ImportedName {
1885 span: sp(),
1886 name: ident("Used"),
1887 alias: None,
1888 }]),
1889 };
1890 let module = simple_module(
1891 vec![import],
1892 vec![Item::Fn(FnDecl {
1893 id: 1,
1894 span: sp(),
1895 annotations: vec![],
1896 visibility: Visibility::Private,
1897 is_async: false,
1898 name: ident("test"),
1899 generic_params: vec![],
1900 params: vec![],
1901 return_type: None,
1902 effect_clause: vec![],
1903 where_clause: vec![],
1904 body: Some(Block {
1905 id: 100,
1906 span: sp(),
1907 stmts: vec![],
1908 tail: Some(Box::new(Expr::Identifier {
1909 id: 99,
1910 span: sp(),
1911 name: ident("Used"),
1912 })),
1913 }),
1914 })],
1915 );
1916 let mut st = SymbolTable::new();
1917 let diag = resolve_names(&module, &mut st);
1918 assert!(!diag.has_errors());
1919 let warnings: Vec<_> = diag
1920 .iter()
1921 .filter(|d| d.severity == bock_errors::Severity::Warning)
1922 .collect();
1923 assert!(warnings.is_empty(), "no warning expected for used import");
1924 }
1925
1926 #[test]
1929 fn let_binding_shadows_outer() {
1930 use bock_ast::LetStmt;
1936 let outer_let = Stmt::Let(LetStmt {
1937 id: 10,
1938 span: sp(),
1939 pattern: Pattern::Bind {
1940 id: 10,
1941 span: sp(),
1942 name: ident("x"),
1943 },
1944 ty: None,
1945 value: Expr::Literal {
1946 id: 11,
1947 span: sp(),
1948 lit: Literal::Int("1".into()),
1949 },
1950 });
1951 let inner_let = Stmt::Let(LetStmt {
1952 id: 20,
1953 span: sp(),
1954 pattern: Pattern::Bind {
1955 id: 20,
1956 span: sp(),
1957 name: ident("x"),
1958 },
1959 ty: None,
1960 value: Expr::Literal {
1961 id: 21,
1962 span: sp(),
1963 lit: Literal::Int("2".into()),
1964 },
1965 });
1966 let use_x = Expr::Identifier {
1967 id: 99,
1968 span: sp(),
1969 name: ident("x"),
1970 };
1971
1972 let module = simple_module(
1973 vec![],
1974 vec![Item::Fn(FnDecl {
1975 id: 1,
1976 span: sp(),
1977 annotations: vec![],
1978 visibility: Visibility::Private,
1979 is_async: false,
1980 name: ident("test"),
1981 generic_params: vec![],
1982 params: vec![],
1983 return_type: None,
1984 effect_clause: vec![],
1985 where_clause: vec![],
1986 body: Some(Block {
1987 id: 100,
1988 span: sp(),
1989 stmts: vec![outer_let, inner_let],
1990 tail: Some(Box::new(use_x)),
1991 }),
1992 })],
1993 );
1994 let mut st = SymbolTable::new();
1995 let diag = resolve_names(&module, &mut st);
1996 assert!(!diag.has_errors());
1997 let resolved = st.resolutions.get(&99).expect("x should be resolved");
1999 assert_eq!(resolved.def_id, 20);
2000 }
2001
2002 #[test]
2005 fn function_param_is_in_scope() {
2006 let module = simple_module(
2007 vec![],
2008 vec![Item::Fn(FnDecl {
2009 id: 1,
2010 span: sp(),
2011 annotations: vec![],
2012 visibility: Visibility::Private,
2013 is_async: false,
2014 name: ident("id"),
2015 generic_params: vec![],
2016 params: vec![Param {
2017 id: 5,
2018 span: sp(),
2019 pattern: Pattern::Bind {
2020 id: 5,
2021 span: sp(),
2022 name: ident("n"),
2023 },
2024 ty: None,
2025 default: None,
2026 }],
2027 return_type: None,
2028 effect_clause: vec![],
2029 where_clause: vec![],
2030 body: Some(Block {
2031 id: 100,
2032 span: sp(),
2033 stmts: vec![],
2034 tail: Some(Box::new(Expr::Identifier {
2035 id: 99,
2036 span: sp(),
2037 name: ident("n"),
2038 })),
2039 }),
2040 })],
2041 );
2042 let mut st = SymbolTable::new();
2043 let diag = resolve_names(&module, &mut st);
2044 assert!(!diag.has_errors());
2045 let resolved = st.resolutions.get(&99).expect("param n should resolve");
2046 assert_eq!(resolved.def_id, 5);
2047 assert_eq!(resolved.kind, NameKind::Variable);
2048 }
2049
2050 #[test]
2053 fn visibility_is_stored_in_binding() {
2054 let module = simple_module(vec![], vec![fn_item(1, "pub_fn", Visibility::Public)]);
2055 let mut st = SymbolTable::new();
2056 resolve_names(&module, &mut st);
2057 let b = st.lookup_peek("pub_fn").expect("pub_fn should be bound");
2058 assert_eq!(b.visibility, Visibility::Public);
2059 }
2060
2061 use crate::registry::{
2064 EnumVariantExport, ExportDetail, ExportKind, ExportedSymbol, ModuleExports,
2065 ModuleRegistry,
2066 };
2067 use crate::stubs::TypeRef;
2068
2069 fn sample_registry() -> ModuleRegistry {
2072 let mut reg = ModuleRegistry::new();
2073 let mut exports = ModuleExports::new("app.models", "src/app/models.bock");
2074 exports.add_symbol(
2075 "User",
2076 ExportedSymbol {
2077 kind: ExportKind::Record,
2078 visibility: Visibility::Public,
2079 ty: TypeRef("User".to_string()),
2080 detail: ExportDetail::Record {
2081 fields: vec![
2082 ("name".to_string(), TypeRef("String".to_string())),
2083 ("age".to_string(), TypeRef("Int".to_string())),
2084 ],
2085 generic_params: vec![],
2086 methods: HashMap::new(),
2087 },
2088 },
2089 );
2090 exports.add_symbol(
2091 "Role",
2092 ExportedSymbol {
2093 kind: ExportKind::Enum,
2094 visibility: Visibility::Public,
2095 ty: TypeRef("Role".to_string()),
2096 detail: ExportDetail::Enum {
2097 variants: vec![],
2098 generic_params: vec![],
2099 },
2100 },
2101 );
2102 exports.add_symbol(
2103 "default_user",
2104 ExportedSymbol {
2105 kind: ExportKind::Function,
2106 visibility: Visibility::Public,
2107 ty: TypeRef("Fn() -> User".to_string()),
2108 detail: ExportDetail::None,
2109 },
2110 );
2111 exports.add_symbol(
2112 "internal_helper",
2113 ExportedSymbol {
2114 kind: ExportKind::Function,
2115 visibility: Visibility::Internal,
2116 ty: TypeRef("Fn() -> Void".to_string()),
2117 detail: ExportDetail::None,
2118 },
2119 );
2120 exports.add_symbol(
2121 "private_secret",
2122 ExportedSymbol {
2123 kind: ExportKind::Function,
2124 visibility: Visibility::Private,
2125 ty: TypeRef("Fn() -> Void".to_string()),
2126 detail: ExportDetail::None,
2127 },
2128 );
2129 reg.register(exports);
2130 reg
2131 }
2132
2133 #[test]
2134 fn registry_named_import_resolves_kind() {
2135 let registry = sample_registry();
2136 let import = ImportDecl {
2137 id: 10,
2138 span: sp(),
2139 visibility: Visibility::Private,
2140 path: mpath(&["app", "models"]),
2141 items: ImportItems::Named(vec![
2142 ImportedName {
2143 span: sp(),
2144 name: ident("User"),
2145 alias: None,
2146 },
2147 ImportedName {
2148 span: sp(),
2149 name: ident("default_user"),
2150 alias: None,
2151 },
2152 ]),
2153 };
2154 let module = simple_module(vec![import], vec![]);
2155 let mut st = SymbolTable::new();
2156 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2157 assert!(
2158 !diag.has_errors(),
2159 "unexpected errors: {:?}",
2160 diag.iter().collect::<Vec<_>>()
2161 );
2162 let user = st.lookup_peek("User").expect("User should be bound");
2163 assert_eq!(user.resolved.kind, NameKind::Type);
2164 assert!(user.is_import);
2165 let dfn = st
2166 .lookup_peek("default_user")
2167 .expect("default_user should be bound");
2168 assert_eq!(dfn.resolved.kind, NameKind::Function);
2169 }
2170
2171 #[test]
2172 fn registry_named_import_with_alias_resolves_kind() {
2173 let registry = sample_registry();
2174 let import = ImportDecl {
2175 id: 10,
2176 span: sp(),
2177 visibility: Visibility::Private,
2178 path: mpath(&["app", "models"]),
2179 items: ImportItems::Named(vec![ImportedName {
2180 span: sp(),
2181 name: ident("User"),
2182 alias: Some(ident("AppUser")),
2183 }]),
2184 };
2185 let module = simple_module(vec![import], vec![]);
2186 let mut st = SymbolTable::new();
2187 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2188 assert!(!diag.has_errors());
2189 assert!(st.lookup_peek("AppUser").is_some());
2190 assert!(st.lookup_peek("User").is_none());
2191 assert_eq!(
2192 st.lookup_peek("AppUser").unwrap().resolved.kind,
2193 NameKind::Type
2194 );
2195 }
2196
2197 #[test]
2198 fn registry_named_import_missing_symbol_produces_error() {
2199 let registry = sample_registry();
2200 let import = ImportDecl {
2201 id: 10,
2202 span: sp(),
2203 visibility: Visibility::Private,
2204 path: mpath(&["app", "models"]),
2205 items: ImportItems::Named(vec![ImportedName {
2206 span: sp(),
2207 name: ident("NonExistent"),
2208 alias: None,
2209 }]),
2210 };
2211 let module = simple_module(vec![import], vec![]);
2212 let mut st = SymbolTable::new();
2213 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2214 assert!(diag.has_errors());
2215 let msgs: Vec<_> = diag.iter().map(|d| d.message.clone()).collect();
2216 assert!(msgs.iter().any(|m| m.contains("NonExistent")));
2217 assert!(msgs.iter().any(|m| m.contains("not exported")));
2218 }
2219
2220 #[test]
2221 fn registry_named_import_private_symbol_produces_error() {
2222 let registry = sample_registry();
2223 let import = ImportDecl {
2224 id: 10,
2225 span: sp(),
2226 visibility: Visibility::Private,
2227 path: mpath(&["app", "models"]),
2228 items: ImportItems::Named(vec![ImportedName {
2229 span: sp(),
2230 name: ident("private_secret"),
2231 alias: None,
2232 }]),
2233 };
2234 let module = simple_module(vec![import], vec![]);
2235 let mut st = SymbolTable::new();
2236 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2237 assert!(diag.has_errors());
2238 let msgs: Vec<_> = diag.iter().map(|d| d.message.clone()).collect();
2239 assert!(msgs.iter().any(|m| m.contains("private")));
2240 }
2241
2242 #[test]
2243 fn registry_named_import_module_not_found_produces_error() {
2244 let registry = sample_registry();
2245 let import = ImportDecl {
2246 id: 10,
2247 span: sp(),
2248 visibility: Visibility::Private,
2249 path: mpath(&["no", "such", "module"]),
2250 items: ImportItems::Named(vec![ImportedName {
2251 span: sp(),
2252 name: ident("Foo"),
2253 alias: None,
2254 }]),
2255 };
2256 let module = simple_module(vec![import], vec![]);
2257 let mut st = SymbolTable::new();
2258 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2259 assert!(diag.has_errors());
2260 let msgs: Vec<_> = diag.iter().map(|d| d.message.clone()).collect();
2261 assert!(msgs.iter().any(|m| m.contains("not found")));
2262 }
2263
2264 #[test]
2265 fn registry_glob_import_defines_all_public_names() {
2266 let registry = sample_registry();
2267 let import = ImportDecl {
2268 id: 10,
2269 span: sp(),
2270 visibility: Visibility::Private,
2271 path: mpath(&["app", "models"]),
2272 items: ImportItems::Glob,
2273 };
2274 let module = simple_module(vec![import], vec![]);
2275 let mut st = SymbolTable::new();
2276 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2277 assert!(
2278 !diag.has_errors(),
2279 "unexpected errors: {:?}",
2280 diag.iter().collect::<Vec<_>>()
2281 );
2282 assert!(st.lookup_peek("User").is_some());
2284 assert!(st.lookup_peek("Role").is_some());
2285 assert!(st.lookup_peek("default_user").is_some());
2286 assert!(st.lookup_peek("internal_helper").is_some());
2288 assert!(
2290 st.lookup_peek("private_secret").is_none()
2291 || st.lookup_peek("private_secret").unwrap().resolved.kind == NameKind::Builtin
2292 );
2293 assert!(st.has_wildcard_import());
2295 }
2296
2297 #[test]
2298 fn registry_glob_import_module_not_found_produces_error() {
2299 let registry = sample_registry();
2300 let import = ImportDecl {
2301 id: 10,
2302 span: sp(),
2303 visibility: Visibility::Private,
2304 path: mpath(&["no", "such", "module"]),
2305 items: ImportItems::Glob,
2306 };
2307 let module = simple_module(vec![import], vec![]);
2308 let mut st = SymbolTable::new();
2309 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2310 assert!(diag.has_errors());
2311 let msgs: Vec<_> = diag.iter().map(|d| d.message.clone()).collect();
2312 assert!(msgs.iter().any(|m| m.contains("not found")));
2313 }
2314
2315 #[test]
2316 fn registry_resolved_import_used_in_body_no_errors() {
2317 let registry = sample_registry();
2320 let import = ImportDecl {
2321 id: 10,
2322 span: sp(),
2323 visibility: Visibility::Private,
2324 path: mpath(&["app", "models"]),
2325 items: ImportItems::Named(vec![
2326 ImportedName {
2327 span: sp(),
2328 name: ident("User"),
2329 alias: None,
2330 },
2331 ImportedName {
2332 span: sp(),
2333 name: ident("default_user"),
2334 alias: None,
2335 },
2336 ]),
2337 };
2338 let module = simple_module(
2339 vec![import],
2340 vec![Item::Fn(FnDecl {
2341 id: 1,
2342 span: sp(),
2343 annotations: vec![],
2344 visibility: Visibility::Private,
2345 is_async: false,
2346 name: ident("main"),
2347 generic_params: vec![],
2348 params: vec![],
2349 return_type: None,
2350 effect_clause: vec![],
2351 where_clause: vec![],
2352 body: Some(Block {
2353 id: 100,
2354 span: sp(),
2355 stmts: vec![],
2356 tail: Some(Box::new(Expr::Call {
2357 id: 50,
2358 span: sp(),
2359 callee: Box::new(Expr::Identifier {
2360 id: 51,
2361 span: sp(),
2362 name: ident("default_user"),
2363 }),
2364 type_args: vec![],
2365 args: vec![],
2366 })),
2367 }),
2368 })],
2369 );
2370 let mut st = SymbolTable::new();
2371 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2372 assert!(
2373 !diag.has_errors(),
2374 "unexpected errors: {:?}",
2375 diag.iter().collect::<Vec<_>>()
2376 );
2377 let resolved = st
2379 .resolutions
2380 .get(&51)
2381 .expect("default_user should resolve");
2382 assert_eq!(resolved.kind, NameKind::Function);
2383 let warnings: Vec<_> = diag
2385 .iter()
2386 .filter(|d| d.severity == bock_errors::Severity::Warning)
2387 .collect();
2388 assert!(
2389 warnings.iter().any(|w| w.message.contains("User")),
2390 "expected unused import warning for User"
2391 );
2392 }
2393
2394 #[test]
2395 fn empty_registry_behaves_like_single_file() {
2396 let registry = ModuleRegistry::new();
2399 let import = ImportDecl {
2400 id: 10,
2401 span: sp(),
2402 visibility: Visibility::Private,
2403 path: mpath(&["unknown", "module"]),
2404 items: ImportItems::Named(vec![ImportedName {
2405 span: sp(),
2406 name: ident("Thing"),
2407 alias: None,
2408 }]),
2409 };
2410 let module = simple_module(vec![import], vec![]);
2411 let mut st = SymbolTable::new();
2412 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2413 assert!(diag.has_errors());
2415 let b = st.lookup_peek("Thing").expect("Thing should still be bound");
2418 assert_eq!(b.resolved.kind, NameKind::Unresolved);
2419 }
2420
2421 #[test]
2422 fn no_registry_leaves_imports_unresolved() {
2423 let import = ImportDecl {
2425 id: 10,
2426 span: sp(),
2427 visibility: Visibility::Private,
2428 path: mpath(&["app", "models"]),
2429 items: ImportItems::Named(vec![ImportedName {
2430 span: sp(),
2431 name: ident("User"),
2432 alias: None,
2433 }]),
2434 };
2435 let module = simple_module(vec![import], vec![]);
2436 let mut st = SymbolTable::new();
2437 let diag = resolve_names(&module, &mut st);
2438 assert!(!diag.has_errors());
2440 let b = st.lookup_peek("User").expect("User should be bound");
2441 assert_eq!(b.resolved.kind, NameKind::Unresolved);
2442 }
2443
2444 #[test]
2445 fn registry_internal_symbol_is_importable() {
2446 let registry = sample_registry();
2447 let import = ImportDecl {
2448 id: 10,
2449 span: sp(),
2450 visibility: Visibility::Private,
2451 path: mpath(&["app", "models"]),
2452 items: ImportItems::Named(vec![ImportedName {
2453 span: sp(),
2454 name: ident("internal_helper"),
2455 alias: None,
2456 }]),
2457 };
2458 let module = simple_module(vec![import], vec![]);
2459 let mut st = SymbolTable::new();
2460 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2461 assert!(
2462 !diag.has_errors(),
2463 "internal symbols should be importable: {:?}",
2464 diag.iter().collect::<Vec<_>>()
2465 );
2466 let b = st
2467 .lookup_peek("internal_helper")
2468 .expect("internal_helper should be bound");
2469 assert_eq!(b.resolved.kind, NameKind::Function);
2470 }
2471
2472 #[test]
2473 fn registry_named_enum_import_seeds_variant_constructors() {
2474 let mut reg = ModuleRegistry::new();
2476 let mut exports = ModuleExports::new("colors", "colors.bock");
2477 exports.add_symbol(
2478 "Color",
2479 ExportedSymbol {
2480 kind: ExportKind::Enum,
2481 visibility: Visibility::Public,
2482 ty: TypeRef("Color".to_string()),
2483 detail: ExportDetail::Enum {
2484 variants: vec![
2485 EnumVariantExport {
2486 name: "Red".to_string(),
2487 constructor_type: None,
2488 fields: None,
2489 },
2490 EnumVariantExport {
2491 name: "Green".to_string(),
2492 constructor_type: None,
2493 fields: None,
2494 },
2495 EnumVariantExport {
2496 name: "Blue".to_string(),
2497 constructor_type: None,
2498 fields: None,
2499 },
2500 ],
2501 generic_params: vec![],
2502 },
2503 },
2504 );
2505 reg.register(exports);
2506
2507 let import = ImportDecl {
2509 id: 10,
2510 span: sp(),
2511 visibility: Visibility::Private,
2512 path: mpath(&["colors"]),
2513 items: ImportItems::Named(vec![ImportedName {
2514 span: sp(),
2515 name: ident("Color"),
2516 alias: None,
2517 }]),
2518 };
2519 let module = simple_module(vec![import], vec![]);
2520 let mut st = SymbolTable::new();
2521 let diag = resolve_names_with_registry(&module, &mut st, ®);
2522 assert!(
2523 !diag.has_errors(),
2524 "unexpected errors: {:?}",
2525 diag.iter().collect::<Vec<_>>()
2526 );
2527 assert!(st.lookup_peek("Color").is_some());
2529 let red = st.lookup_peek("Red").expect("Red should be in scope");
2531 assert_eq!(red.resolved.kind, NameKind::Function);
2532 assert!(!red.is_import);
2535 assert!(st.lookup_peek("Green").is_some());
2536 assert!(st.lookup_peek("Blue").is_some());
2537 }
2538
2539 #[test]
2540 fn registry_glob_enum_import_seeds_variant_constructors() {
2541 let mut reg = ModuleRegistry::new();
2543 let mut exports = ModuleExports::new("colors", "colors.bock");
2544 exports.add_symbol(
2545 "Color",
2546 ExportedSymbol {
2547 kind: ExportKind::Enum,
2548 visibility: Visibility::Public,
2549 ty: TypeRef("Color".to_string()),
2550 detail: ExportDetail::Enum {
2551 variants: vec![
2552 EnumVariantExport {
2553 name: "Red".to_string(),
2554 constructor_type: None,
2555 fields: None,
2556 },
2557 ],
2558 generic_params: vec![],
2559 },
2560 },
2561 );
2562 reg.register(exports);
2563
2564 let import = ImportDecl {
2566 id: 10,
2567 span: sp(),
2568 visibility: Visibility::Private,
2569 path: mpath(&["colors"]),
2570 items: ImportItems::Glob,
2571 };
2572 let module = simple_module(vec![import], vec![]);
2573 let mut st = SymbolTable::new();
2574 let diag = resolve_names_with_registry(&module, &mut st, ®);
2575 assert!(
2576 !diag.has_errors(),
2577 "unexpected errors: {:?}",
2578 diag.iter().collect::<Vec<_>>()
2579 );
2580 assert!(st.lookup_peek("Color").is_some());
2581 let red = st.lookup_peek("Red").expect("Red should be in scope via glob");
2582 assert_eq!(red.resolved.kind, NameKind::Function);
2583 assert!(!red.is_import);
2584 }
2585
2586 #[test]
2587 fn registry_glob_with_body_resolves_names() {
2588 let registry = sample_registry();
2591 let import = ImportDecl {
2592 id: 10,
2593 span: sp(),
2594 visibility: Visibility::Private,
2595 path: mpath(&["app", "models"]),
2596 items: ImportItems::Glob,
2597 };
2598 let module = simple_module(
2599 vec![import],
2600 vec![Item::Fn(FnDecl {
2601 id: 1,
2602 span: sp(),
2603 annotations: vec![],
2604 visibility: Visibility::Private,
2605 is_async: false,
2606 name: ident("test"),
2607 generic_params: vec![],
2608 params: vec![],
2609 return_type: None,
2610 effect_clause: vec![],
2611 where_clause: vec![],
2612 body: Some(Block {
2613 id: 100,
2614 span: sp(),
2615 stmts: vec![],
2616 tail: Some(Box::new(Expr::Identifier {
2617 id: 99,
2618 span: sp(),
2619 name: ident("User"),
2620 })),
2621 }),
2622 })],
2623 );
2624 let mut st = SymbolTable::new();
2625 let diag = resolve_names_with_registry(&module, &mut st, ®istry);
2626 assert!(
2627 !diag.has_errors(),
2628 "unexpected errors: {:?}",
2629 diag.iter().collect::<Vec<_>>()
2630 );
2631 let resolved = st
2632 .resolutions
2633 .get(&99)
2634 .expect("User should resolve from glob import");
2635 assert_eq!(resolved.kind, NameKind::Type);
2636 }
2637}