1use std::collections::HashMap;
19
20use bock_ast::{
21 Block, Expr, FnDecl, ForLoop, GuardStmt, HandlingBlock, ImplBlock, Item, LetStmt, LoopStmt,
22 MatchArm, Module, NodeId, Param, Pattern, RecordPatternField, Stmt, WhileLoop,
23};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct ScopeId(pub u32);
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct Binding {
34 pub name: String,
36 pub node_id: NodeId,
38 pub mutable: bool,
40}
41
42#[derive(Debug)]
44pub struct Scope {
45 pub id: ScopeId,
47 pub parent: Option<ScopeId>,
49 pub bindings: Vec<Binding>,
51}
52
53impl Scope {
54 fn new(id: ScopeId, parent: Option<ScopeId>) -> Self {
55 Self {
56 id,
57 parent,
58 bindings: Vec::new(),
59 }
60 }
61
62 fn add_binding(&mut self, name: String, node_id: NodeId, mutable: bool) {
63 self.bindings.push(Binding {
64 name,
65 node_id,
66 mutable,
67 });
68 }
69}
70
71pub struct ScopeTree {
76 scopes: HashMap<ScopeId, Scope>,
77 next_id: u32,
78}
79
80impl Default for ScopeTree {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl ScopeTree {
87 #[must_use]
89 pub fn new() -> Self {
90 let root = Scope::new(ScopeId(0), None);
91 let mut scopes = HashMap::new();
92 scopes.insert(ScopeId(0), root);
93 Self { scopes, next_id: 1 }
94 }
95
96 #[must_use]
98 pub fn root(&self) -> ScopeId {
99 ScopeId(0)
100 }
101
102 #[must_use]
104 pub fn get(&self, id: ScopeId) -> Option<&Scope> {
105 self.scopes.get(&id)
106 }
107
108 #[must_use]
110 pub fn scope_count(&self) -> usize {
111 self.scopes.len()
112 }
113
114 #[must_use]
118 pub fn lookup(&self, name: &str, from: ScopeId) -> Option<&Binding> {
119 let mut current = Some(from);
120 while let Some(id) = current {
121 if let Some(scope) = self.scopes.get(&id) {
122 if let Some(b) = scope.bindings.iter().rev().find(|b| b.name == name) {
124 return Some(b);
125 }
126 current = scope.parent;
127 } else {
128 break;
129 }
130 }
131 None
132 }
133
134 fn alloc(&mut self, parent: ScopeId) -> ScopeId {
136 let id = ScopeId(self.next_id);
137 self.next_id += 1;
138 self.scopes.insert(id, Scope::new(id, Some(parent)));
139 id
140 }
141
142 fn bind(&mut self, scope_id: ScopeId, name: String, node_id: NodeId, mutable: bool) {
144 if let Some(scope) = self.scopes.get_mut(&scope_id) {
145 scope.add_binding(name, node_id, mutable);
146 }
147 }
148}
149
150#[must_use]
157pub fn build_scope_tree(module: &Module) -> ScopeTree {
158 let mut tree = ScopeTree::new();
159 let root = tree.root();
160 let mut builder = ScopeBuilder { tree: &mut tree };
161 builder.visit_module(module, root);
162 tree
163}
164
165struct ScopeBuilder<'t> {
168 tree: &'t mut ScopeTree,
169}
170
171impl<'t> ScopeBuilder<'t> {
172 fn visit_module(&mut self, module: &Module, scope: ScopeId) {
173 for item in &module.items {
175 self.collect_item_name(item, scope);
176 }
177 for item in &module.items {
179 self.visit_item(item, scope);
180 }
181 }
182
183 fn collect_item_name(&mut self, item: &Item, scope: ScopeId) {
184 match item {
185 Item::Fn(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
186 Item::Record(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
187 Item::Enum(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
188 Item::Class(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
189 Item::Trait(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
190 Item::Effect(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
191 Item::TypeAlias(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
192 Item::Const(d) => self.tree.bind(scope, d.name.name.clone(), d.id, false),
193 Item::Impl(_)
194 | Item::ModuleHandle(_)
195 | Item::PropertyTest(_)
196 | Item::Error { .. }
197 | Item::PlatformTrait(_) => {}
198 }
199 }
200
201 fn visit_item(&mut self, item: &Item, parent: ScopeId) {
202 match item {
203 Item::Fn(d) => self.visit_fn(d, parent),
204 Item::Class(d) => {
205 for method in &d.methods {
206 self.visit_fn(method, parent);
207 }
208 }
209 Item::Trait(d) => {
210 for method in &d.methods {
211 self.visit_fn(method, parent);
212 }
213 }
214 Item::Effect(d) => {
215 for op in &d.operations {
216 self.visit_fn(op, parent);
217 }
218 }
219 Item::Impl(d) => self.visit_impl(d, parent),
220 Item::Const(d) => self.visit_expr(&d.value, parent),
221 Item::Record(_)
222 | Item::Enum(_)
223 | Item::TypeAlias(_)
224 | Item::ModuleHandle(_)
225 | Item::PropertyTest(_)
226 | Item::PlatformTrait(_)
227 | Item::Error { .. } => {}
228 }
229 }
230
231 fn visit_impl(&mut self, impl_block: &ImplBlock, parent: ScopeId) {
232 for method in &impl_block.methods {
233 self.visit_fn(method, parent);
234 }
235 }
236
237 fn visit_fn(&mut self, decl: &FnDecl, parent: ScopeId) {
239 let fn_scope = self.tree.alloc(parent);
240 for param in &decl.params {
241 self.bind_param(param, fn_scope);
242 }
243 if let Some(ref body) = decl.body {
244 self.visit_block(body, fn_scope);
245 }
246 }
247
248 fn bind_param(&mut self, param: &Param, scope: ScopeId) {
249 self.bind_pattern(¶m.pattern, scope);
250 if let Some(default) = ¶m.default {
251 self.visit_expr(default, scope);
252 }
253 }
254
255 fn visit_block(&mut self, block: &Block, parent: ScopeId) {
256 let block_scope = self.tree.alloc(parent);
257 for stmt in &block.stmts {
258 self.visit_stmt(stmt, block_scope);
259 }
260 if let Some(tail) = &block.tail {
261 self.visit_expr(tail, block_scope);
262 }
263 }
264
265 fn visit_stmt(&mut self, stmt: &Stmt, scope: ScopeId) {
266 match stmt {
267 Stmt::Let(let_stmt) => self.visit_let(let_stmt, scope),
268 Stmt::Expr(expr) => self.visit_expr(expr, scope),
269 Stmt::For(for_loop) => self.visit_for(for_loop, scope),
270 Stmt::While(while_loop) => self.visit_while(while_loop, scope),
271 Stmt::Loop(loop_stmt) => self.visit_loop(loop_stmt, scope),
272 Stmt::Guard(guard) => self.visit_guard(guard, scope),
273 Stmt::Handling(handling) => self.visit_handling(handling, scope),
274 Stmt::Empty => {}
275 }
276 }
277
278 fn visit_let(&mut self, let_stmt: &LetStmt, scope: ScopeId) {
279 self.visit_expr(&let_stmt.value, scope);
281 self.bind_pattern(&let_stmt.pattern, scope);
283 }
284
285 fn visit_for(&mut self, for_loop: &ForLoop, parent: ScopeId) {
286 self.visit_expr(&for_loop.iterable, parent);
288 let loop_scope = self.tree.alloc(parent);
290 self.bind_pattern(&for_loop.pattern, loop_scope);
291 self.visit_block(&for_loop.body, loop_scope);
292 }
293
294 fn visit_while(&mut self, while_loop: &WhileLoop, parent: ScopeId) {
295 self.visit_expr(&while_loop.condition, parent);
296 self.visit_block(&while_loop.body, parent);
297 }
298
299 fn visit_loop(&mut self, loop_stmt: &LoopStmt, parent: ScopeId) {
300 self.visit_block(&loop_stmt.body, parent);
301 }
302
303 fn visit_guard(&mut self, guard: &GuardStmt, parent: ScopeId) {
304 if let Some(pat) = &guard.let_pattern {
305 self.bind_pattern(pat, parent);
306 }
307 self.visit_expr(&guard.condition, parent);
308 self.visit_block(&guard.else_block, parent);
309 }
310
311 fn visit_handling(&mut self, handling: &HandlingBlock, parent: ScopeId) {
312 let handling_scope = self.tree.alloc(parent);
314 for pair in &handling.handlers {
316 self.visit_expr(&pair.handler, parent);
317 }
318 self.visit_block(&handling.body, handling_scope);
319 }
320
321 fn visit_expr(&mut self, expr: &Expr, scope: ScopeId) {
322 match expr {
323 Expr::Literal { .. }
324 | Expr::Identifier { .. }
325 | Expr::Continue { .. }
326 | Expr::Unreachable { .. }
327 | Expr::Placeholder { .. } => {}
328
329 Expr::Binary { left, right, .. }
330 | Expr::Compose { left, right, .. }
331 | Expr::Pipe { left, right, .. }
332 | Expr::Range {
333 lo: left,
334 hi: right,
335 ..
336 } => {
337 self.visit_expr(left, scope);
338 self.visit_expr(right, scope);
339 }
340
341 Expr::Unary { operand, .. }
342 | Expr::Try { expr: operand, .. }
343 | Expr::Await { expr: operand, .. } => {
344 self.visit_expr(operand, scope);
345 }
346
347 Expr::Assign { target, value, .. } => {
348 self.visit_expr(target, scope);
349 self.visit_expr(value, scope);
350 }
351
352 Expr::Call { callee, args, .. } => {
353 self.visit_expr(callee, scope);
354 for arg in args {
355 self.visit_expr(&arg.value, scope);
356 }
357 }
358
359 Expr::MethodCall { receiver, args, .. } => {
360 self.visit_expr(receiver, scope);
361 for arg in args {
362 self.visit_expr(&arg.value, scope);
363 }
364 }
365
366 Expr::FieldAccess { object, .. } => {
367 self.visit_expr(object, scope);
368 }
369
370 Expr::Index { object, index, .. } => {
371 self.visit_expr(object, scope);
372 self.visit_expr(index, scope);
373 }
374
375 Expr::Lambda { params, body, .. } => {
376 let lambda_scope = self.tree.alloc(scope);
377 for param in params {
378 self.bind_param(param, lambda_scope);
379 }
380 self.visit_expr(body, lambda_scope);
381 }
382
383 Expr::If {
384 let_pattern,
385 condition,
386 then_block,
387 else_block,
388 ..
389 } => {
390 self.visit_expr(condition, scope);
391 if let Some(pat) = let_pattern {
392 let then_scope = self.tree.alloc(scope);
394 self.bind_pattern(pat, then_scope);
395 self.visit_block(then_block, then_scope);
396 } else {
397 self.visit_block(then_block, scope);
398 }
399 if let Some(else_expr) = else_block {
400 self.visit_expr(else_expr, scope);
401 }
402 }
403
404 Expr::Match {
405 scrutinee, arms, ..
406 } => {
407 self.visit_expr(scrutinee, scope);
408 for arm in arms {
409 self.visit_match_arm(arm, scope);
410 }
411 }
412
413 Expr::Loop { body, .. } => {
414 self.visit_block(body, scope);
415 }
416
417 Expr::Block { block, .. } => {
418 self.visit_block(block, scope);
419 }
420
421 Expr::RecordConstruct { fields, spread, .. } => {
422 for field in fields {
423 if let Some(val) = &field.value {
424 self.visit_expr(val, scope);
425 }
426 }
427 if let Some(s) = spread {
428 self.visit_expr(&s.expr, scope);
429 }
430 }
431
432 Expr::ListLiteral { elems, .. }
433 | Expr::SetLiteral { elems, .. }
434 | Expr::TupleLiteral { elems, .. } => {
435 for elem in elems {
436 self.visit_expr(elem, scope);
437 }
438 }
439
440 Expr::MapLiteral { entries, .. } => {
441 for (k, v) in entries {
442 self.visit_expr(k, scope);
443 self.visit_expr(v, scope);
444 }
445 }
446
447 Expr::Return { value, .. } | Expr::Break { value, .. } => {
448 if let Some(val) = value {
449 self.visit_expr(val, scope);
450 }
451 }
452
453 Expr::Interpolation { parts, .. } => {
454 for part in parts {
455 if let bock_ast::InterpolationPart::Expr(e) = part {
456 self.visit_expr(e, scope);
457 }
458 }
459 }
460
461 Expr::Is { expr, .. } => {
462 self.visit_expr(expr, scope);
463 }
464 }
465 }
466
467 fn visit_match_arm(&mut self, arm: &MatchArm, parent: ScopeId) {
468 let arm_scope = self.tree.alloc(parent);
470 self.bind_pattern(&arm.pattern, arm_scope);
471 if let Some(guard) = &arm.guard {
472 self.visit_expr(guard, arm_scope);
473 }
474 self.visit_expr(&arm.body, arm_scope);
475 }
476
477 fn bind_pattern(&mut self, pat: &Pattern, scope: ScopeId) {
479 match pat {
480 Pattern::Bind { id, name, .. } => {
481 self.tree.bind(scope, name.name.clone(), *id, false);
482 }
483 Pattern::MutBind { id, name, .. } => {
484 self.tree.bind(scope, name.name.clone(), *id, true);
485 }
486 Pattern::Wildcard { .. } | Pattern::Literal { .. } | Pattern::Rest { .. } => {}
487
488 Pattern::Constructor { fields, .. } => {
489 for field in fields {
490 self.bind_pattern(field, scope);
491 }
492 }
493 Pattern::Record { fields, .. } => {
494 for field in fields {
495 self.bind_record_pattern_field(field, scope);
496 }
497 }
498 Pattern::Tuple { elems, .. } => {
499 for elem in elems {
500 self.bind_pattern(elem, scope);
501 }
502 }
503 Pattern::List { elems, rest, .. } => {
504 for elem in elems {
505 self.bind_pattern(elem, scope);
506 }
507 if let Some(r) = rest {
508 self.bind_pattern(r, scope);
509 }
510 }
511 Pattern::Or { alternatives, .. } => {
512 if let Some(first) = alternatives.first() {
514 self.bind_pattern(first, scope);
515 }
516 }
517 Pattern::Range { lo, hi, .. } => {
518 self.bind_pattern(lo, scope);
519 self.bind_pattern(hi, scope);
520 }
521 }
522 }
523
524 fn bind_record_pattern_field(&mut self, field: &RecordPatternField, scope: ScopeId) {
525 if let Some(pat) = &field.pattern {
526 self.bind_pattern(pat, scope);
527 } else {
528 self.tree.bind(scope, field.name.name.clone(), 0, false);
531 }
532 }
533}
534
535#[cfg(test)]
538mod tests {
539 use super::*;
540 use bock_ast::{Block, FnDecl, Ident, Item, LetStmt, Literal, Module, Pattern, Stmt};
541 use bock_errors::{FileId, Span};
542
543 fn span() -> Span {
544 Span {
545 file: FileId(0),
546 start: 0,
547 end: 0,
548 }
549 }
550
551 fn ident(name: &str) -> Ident {
552 Ident {
553 name: name.to_string(),
554 span: span(),
555 }
556 }
557
558 fn make_module(items: Vec<Item>) -> Module {
559 Module {
560 id: 0,
561 span: span(),
562 doc: vec![],
563 path: None,
564 imports: vec![],
565 items,
566 }
567 }
568
569 fn empty_block(id: NodeId) -> Block {
570 Block {
571 id,
572 span: span(),
573 stmts: vec![],
574 tail: None,
575 }
576 }
577
578 fn simple_fn(id: NodeId, name: &str, body: Block) -> Item {
579 Item::Fn(FnDecl {
580 id,
581 span: span(),
582 annotations: vec![],
583 visibility: bock_ast::Visibility::Private,
584 is_async: false,
585 name: ident(name),
586 generic_params: vec![],
587 params: vec![],
588 return_type: None,
589 effect_clause: vec![],
590 where_clause: vec![],
591 body: Some(body),
592 })
593 }
594
595 #[test]
596 fn root_scope_exists() {
597 let module = make_module(vec![]);
598 let tree = build_scope_tree(&module);
599 assert!(tree.get(tree.root()).is_some());
600 }
601
602 #[test]
603 fn top_level_fn_bound_in_root() {
604 let module = make_module(vec![simple_fn(1, "foo", empty_block(2))]);
605 let tree = build_scope_tree(&module);
606 let found = tree.lookup("foo", tree.root());
607 assert!(found.is_some());
608 assert_eq!(found.unwrap().name, "foo");
609 }
610
611 #[test]
612 fn fn_creates_child_scope() {
613 let module = make_module(vec![simple_fn(1, "bar", empty_block(2))]);
614 let tree = build_scope_tree(&module);
615 assert!(tree.scope_count() >= 3);
617 }
618
619 #[test]
620 fn let_binding_visible_in_scope() {
621 let let_stmt = Stmt::Let(LetStmt {
623 id: 10,
624 span: span(),
625 pattern: Pattern::Bind {
626 id: 11,
627 span: span(),
628 name: ident("x"),
629 },
630 ty: None,
631 value: Expr::Literal {
632 id: 12,
633 span: span(),
634 lit: Literal::Unit,
635 },
636 });
637 let body = Block {
638 id: 2,
639 span: span(),
640 stmts: vec![let_stmt],
641 tail: None,
642 };
643 let module = make_module(vec![simple_fn(1, "foo", body)]);
644 let tree = build_scope_tree(&module);
645
646 let x_in_root = tree.lookup("x", tree.root());
650 assert!(x_in_root.is_none());
652
653 assert!(tree.lookup("foo", tree.root()).is_some());
655 }
656
657 #[test]
658 fn shadowing_in_nested_scopes() {
659 let inner_let = Stmt::Let(LetStmt {
662 id: 20,
663 span: span(),
664 pattern: Pattern::Bind {
665 id: 21,
666 span: span(),
667 name: ident("x"),
668 },
669 ty: None,
670 value: Expr::Literal {
671 id: 22,
672 span: span(),
673 lit: Literal::Unit,
674 },
675 });
676 let inner_body = Block {
677 id: 30,
678 span: span(),
679 stmts: vec![inner_let],
680 tail: None,
681 };
682 let outer_let = Stmt::Let(LetStmt {
683 id: 40,
684 span: span(),
685 pattern: Pattern::Bind {
686 id: 41,
687 span: span(),
688 name: ident("x"),
689 },
690 ty: None,
691 value: Expr::Literal {
692 id: 42,
693 span: span(),
694 lit: Literal::Unit,
695 },
696 });
697 let inner_fn_item = Item::Fn(FnDecl {
698 id: 50,
699 span: span(),
700 annotations: vec![],
701 visibility: bock_ast::Visibility::Private,
702 is_async: false,
703 name: ident("inner"),
704 generic_params: vec![],
705 params: vec![],
706 return_type: None,
707 effect_clause: vec![],
708 where_clause: vec![],
709 body: Some(inner_body),
710 });
711 let outer_body = Block {
713 id: 60,
714 span: span(),
715 stmts: vec![outer_let],
716 tail: None,
717 };
718 let module = make_module(vec![simple_fn(1, "outer", outer_body), inner_fn_item]);
719 let tree = build_scope_tree(&module);
720 assert!(tree.lookup("outer", tree.root()).is_some());
722 assert!(tree.lookup("inner", tree.root()).is_some());
723 }
724
725 #[test]
726 fn match_arm_creates_scope() {
727 let arm = MatchArm {
729 id: 5,
730 span: span(),
731 pattern: Pattern::Bind {
732 id: 6,
733 span: span(),
734 name: ident("x"),
735 },
736 guard: None,
737 body: Expr::Literal {
738 id: 7,
739 span: span(),
740 lit: Literal::Unit,
741 },
742 };
743 let match_expr = Expr::Match {
744 id: 4,
745 span: span(),
746 scrutinee: Box::new(Expr::Literal {
747 id: 3,
748 span: span(),
749 lit: Literal::Unit,
750 }),
751 arms: vec![arm],
752 };
753 let body = Block {
754 id: 2,
755 span: span(),
756 stmts: vec![],
757 tail: Some(Box::new(match_expr)),
758 };
759 let module = make_module(vec![simple_fn(1, "test", body)]);
760 let tree = build_scope_tree(&module);
761 assert!(tree.scope_count() >= 4);
763 }
764
765 #[test]
766 fn mut_binding_flagged() {
767 let let_stmt = Stmt::Let(LetStmt {
769 id: 10,
770 span: span(),
771 pattern: Pattern::MutBind {
772 id: 11,
773 span: span(),
774 name: ident("y"),
775 },
776 ty: None,
777 value: Expr::Literal {
778 id: 12,
779 span: span(),
780 lit: Literal::Unit,
781 },
782 });
783 let body = Block {
784 id: 2,
785 span: span(),
786 stmts: vec![let_stmt],
787 tail: None,
788 };
789 let module = make_module(vec![simple_fn(1, "test", body)]);
790 let tree = build_scope_tree(&module);
791
792 let found = (0..tree.next_id)
794 .filter_map(|i| tree.get(ScopeId(i)))
795 .flat_map(|s| s.bindings.iter())
796 .find(|b| b.name == "y");
797
798 assert!(found.is_some());
799 assert!(found.unwrap().mutable);
800 }
801
802 #[test]
803 fn lookup_walks_parent_chain() {
804 let module = make_module(vec![simple_fn(1, "greet", empty_block(2))]);
806 let tree = build_scope_tree(&module);
807 let found = tree.lookup("greet", ScopeId(1));
809 assert!(found.is_some());
810 }
811}