1use std::borrow::Cow;
20use std::ops::ControlFlow;
21
22use crate::ast::*;
23use crate::visitor::{walk_enum_member, walk_stmt, Visitor};
24use crate::Span;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum SymbolKind {
29 Function,
30 Class,
31 Interface,
32 Trait,
33 Enum,
34 Constant,
35 Method,
36 Property,
37 ClassConstant,
38 EnumCase,
39}
40
41#[derive(Debug, Clone)]
43pub struct Symbol<'src> {
44 pub name: Cow<'src, str>,
46 pub fqn: String,
48 pub kind: SymbolKind,
49 pub span: Span,
50 pub visibility: Option<Visibility>,
52 pub parent: Option<String>,
54}
55
56#[derive(Debug, Clone)]
58pub struct UseImport<'src> {
59 pub name: Cow<'src, str>,
61 pub alias: Option<&'src str>,
63 pub kind: UseKind,
64 pub span: Span,
65}
66
67impl<'src> UseImport<'src> {
68 pub fn local_name(&self) -> &str {
70 if let Some(alias) = self.alias {
71 return alias;
72 }
73 self.name.rsplit('\\').next().unwrap_or(self.name.as_ref())
74 }
75}
76
77pub struct SymbolTable<'src> {
79 symbols: Vec<Symbol<'src>>,
80 imports: Vec<UseImport<'src>>,
81}
82
83impl<'src> SymbolTable<'src> {
84 pub fn build<'arena>(program: &Program<'arena, 'src>) -> Self {
86 let mut collector = SymbolCollector {
87 symbols: Vec::new(),
88 imports: Vec::new(),
89 namespace: String::new(),
90 };
91 let _ = collector.visit_program(program);
92 Self {
93 symbols: collector.symbols,
94 imports: collector.imports,
95 }
96 }
97
98 pub fn symbols(&self) -> &[Symbol<'src>] {
100 &self.symbols
101 }
102
103 pub fn imports(&self) -> &[UseImport<'src>] {
105 &self.imports
106 }
107
108 pub fn symbols_of_kind(&self, kind: SymbolKind) -> impl Iterator<Item = &Symbol<'src>> {
110 self.symbols.iter().filter(move |s| s.kind == kind)
111 }
112
113 pub fn find_by_fqn(&self, fqn: &str) -> Option<&Symbol<'src>> {
115 self.symbols.iter().find(|s| s.fqn == fqn)
116 }
117
118 pub fn symbols_at(&self, offset: u32) -> Vec<&Symbol<'src>> {
120 self.symbols
121 .iter()
122 .filter(|s| s.span.start <= offset && offset < s.span.end)
123 .collect()
124 }
125
126 pub fn resolve_name(&self, name: &str) -> Option<&str> {
129 for import in &self.imports {
131 if import.local_name() == name {
132 return Some(&import.name);
133 }
134 }
135 for sym in &self.symbols {
137 if sym.name == name && sym.parent.is_none() {
138 return Some(&sym.fqn);
139 }
140 }
141 None
142 }
143
144 pub fn functions(&self) -> impl Iterator<Item = &Symbol<'src>> {
146 self.symbols_of_kind(SymbolKind::Function)
147 }
148
149 pub fn classes(&self) -> impl Iterator<Item = &Symbol<'src>> {
151 self.symbols_of_kind(SymbolKind::Class)
152 }
153
154 pub fn interfaces(&self) -> impl Iterator<Item = &Symbol<'src>> {
156 self.symbols_of_kind(SymbolKind::Interface)
157 }
158
159 pub fn traits(&self) -> impl Iterator<Item = &Symbol<'src>> {
161 self.symbols_of_kind(SymbolKind::Trait)
162 }
163
164 pub fn enums(&self) -> impl Iterator<Item = &Symbol<'src>> {
166 self.symbols_of_kind(SymbolKind::Enum)
167 }
168
169 pub fn constants(&self) -> impl Iterator<Item = &Symbol<'src>> {
171 self.symbols_of_kind(SymbolKind::Constant)
172 }
173
174 pub fn members_of<'a>(&'a self, parent_fqn: &'a str) -> impl Iterator<Item = &'a Symbol<'src>> {
176 self.symbols
177 .iter()
178 .filter(move |s| s.parent.as_deref() == Some(parent_fqn))
179 }
180}
181
182struct SymbolCollector<'src> {
183 symbols: Vec<Symbol<'src>>,
184 imports: Vec<UseImport<'src>>,
185 namespace: String,
186}
187
188impl<'src> SymbolCollector<'src> {
189 fn fqn(&self, name: &str) -> String {
190 if self.namespace.is_empty() {
191 name.to_string()
192 } else {
193 format!("{}\\{}", self.namespace, name)
194 }
195 }
196
197 fn add_class_members<'arena>(
198 &mut self,
199 parent_fqn: &str,
200 members: &[ClassMember<'arena, 'src>],
201 ) {
202 for member in members {
203 match &member.kind {
204 ClassMemberKind::Method(method) => {
205 self.symbols.push(Symbol {
206 name: Cow::Borrowed(method.name),
207 fqn: format!("{}::{}", parent_fqn, method.name),
208 kind: SymbolKind::Method,
209 span: member.span,
210 visibility: method.visibility,
211 parent: Some(parent_fqn.to_string()),
212 });
213 }
214 ClassMemberKind::Property(prop) => {
215 self.symbols.push(Symbol {
216 name: Cow::Borrowed(prop.name),
217 fqn: format!("{}::${}", parent_fqn, prop.name),
218 kind: SymbolKind::Property,
219 span: member.span,
220 visibility: prop.visibility,
221 parent: Some(parent_fqn.to_string()),
222 });
223 }
224 ClassMemberKind::ClassConst(cc) => {
225 self.symbols.push(Symbol {
226 name: Cow::Borrowed(cc.name),
227 fqn: format!("{}::{}", parent_fqn, cc.name),
228 kind: SymbolKind::ClassConstant,
229 span: member.span,
230 visibility: cc.visibility,
231 parent: Some(parent_fqn.to_string()),
232 });
233 }
234 ClassMemberKind::TraitUse(_) => {}
235 }
236 }
237 }
238}
239
240impl<'arena, 'src> Visitor<'arena, 'src> for SymbolCollector<'src> {
241 fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
242 match &stmt.kind {
243 StmtKind::Namespace(ns) => {
244 if let Some(name) = &ns.name {
245 self.namespace = name.join_parts().into_owned();
246 } else {
247 self.namespace.clear();
248 }
249 match &ns.body {
250 NamespaceBody::Braced(stmts) => {
251 for s in stmts.iter() {
252 self.visit_stmt(s)?;
253 }
254 self.namespace.clear();
256 }
257 NamespaceBody::Simple => {
258 }
261 }
262 return ControlFlow::Continue(());
263 }
264 StmtKind::Use(use_decl) => {
265 for item in use_decl.uses.iter() {
266 self.imports.push(UseImport {
267 name: item.name.join_parts(),
268 alias: item.alias,
269 kind: item.kind.unwrap_or(use_decl.kind),
270 span: item.name.span(),
271 });
272 }
273 return ControlFlow::Continue(());
274 }
275 StmtKind::Function(func) => {
276 self.symbols.push(Symbol {
277 name: Cow::Borrowed(func.name),
278 fqn: self.fqn(func.name),
279 kind: SymbolKind::Function,
280 span: stmt.span,
281 visibility: None,
282 parent: None,
283 });
284 }
285 StmtKind::Class(class) => {
286 if let Some(name) = class.name {
287 let fqn = self.fqn(name);
288 self.symbols.push(Symbol {
289 name: Cow::Borrowed(name),
290 fqn: fqn.clone(),
291 kind: SymbolKind::Class,
292 span: stmt.span,
293 visibility: None,
294 parent: None,
295 });
296 self.add_class_members(&fqn, &class.members);
297 }
298 }
299 StmtKind::Interface(iface) => {
300 let fqn = self.fqn(iface.name);
301 self.symbols.push(Symbol {
302 name: Cow::Borrowed(iface.name),
303 fqn: fqn.clone(),
304 kind: SymbolKind::Interface,
305 span: stmt.span,
306 visibility: None,
307 parent: None,
308 });
309 self.add_class_members(&fqn, &iface.members);
310 }
311 StmtKind::Trait(trait_decl) => {
312 let fqn = self.fqn(trait_decl.name);
313 self.symbols.push(Symbol {
314 name: Cow::Borrowed(trait_decl.name),
315 fqn: fqn.clone(),
316 kind: SymbolKind::Trait,
317 span: stmt.span,
318 visibility: None,
319 parent: None,
320 });
321 self.add_class_members(&fqn, &trait_decl.members);
322 }
323 StmtKind::Enum(enum_decl) => {
324 let fqn = self.fqn(enum_decl.name);
325 self.symbols.push(Symbol {
326 name: Cow::Borrowed(enum_decl.name),
327 fqn: fqn.clone(),
328 kind: SymbolKind::Enum,
329 span: stmt.span,
330 visibility: None,
331 parent: None,
332 });
333 for member in enum_decl.members.iter() {
334 match &member.kind {
335 EnumMemberKind::Case(case) => {
336 self.symbols.push(Symbol {
337 name: Cow::Borrowed(case.name),
338 fqn: format!("{}::{}", fqn, case.name),
339 kind: SymbolKind::EnumCase,
340 span: member.span,
341 visibility: None,
342 parent: Some(fqn.clone()),
343 });
344 }
345 EnumMemberKind::Method(method) => {
346 self.symbols.push(Symbol {
347 name: Cow::Borrowed(method.name),
348 fqn: format!("{}::{}", fqn, method.name),
349 kind: SymbolKind::Method,
350 span: member.span,
351 visibility: method.visibility,
352 parent: Some(fqn.clone()),
353 });
354 }
355 EnumMemberKind::ClassConst(cc) => {
356 self.symbols.push(Symbol {
357 name: Cow::Borrowed(cc.name),
358 fqn: format!("{}::{}", fqn, cc.name),
359 kind: SymbolKind::ClassConstant,
360 span: member.span,
361 visibility: cc.visibility,
362 parent: Some(fqn.clone()),
363 });
364 }
365 EnumMemberKind::TraitUse(_) => {}
366 }
367 walk_enum_member(self, member)?;
368 }
369 return ControlFlow::Continue(());
370 }
371 StmtKind::Const(items) => {
372 for item in items.iter() {
373 self.symbols.push(Symbol {
374 name: Cow::Borrowed(item.name),
375 fqn: self.fqn(item.name),
376 kind: SymbolKind::Constant,
377 span: item.span,
378 visibility: None,
379 parent: None,
380 });
381 }
382 }
383 _ => {}
384 }
385 walk_stmt(self, stmt)
386 }
387
388 fn visit_expr(&mut self, _expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
390 ControlFlow::Continue(())
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use crate::ast::ArenaVec;
398
399 fn build_table<'arena, 'src>(
401 _arena: &'arena bumpalo::Bump,
402 stmts: ArenaVec<'arena, Stmt<'arena, 'src>>,
403 ) -> SymbolTable<'src> {
404 let program = Program {
405 stmts,
406 span: Span::DUMMY,
407 };
408 SymbolTable::build(&program)
409 }
410
411 #[test]
412 fn collects_function() {
413 let arena = bumpalo::Bump::new();
414 let func = arena.alloc(FunctionDecl {
415 name: "doStuff",
416 params: ArenaVec::new_in(&arena),
417 body: ArenaVec::new_in(&arena),
418 return_type: None,
419 by_ref: false,
420 attributes: ArenaVec::new_in(&arena),
421 doc_comment: None,
422 });
423 let mut stmts = ArenaVec::new_in(&arena);
424 stmts.push(Stmt {
425 kind: StmtKind::Function(func),
426 span: Span::new(0, 30),
427 });
428
429 let table = build_table(&arena, stmts);
430 assert_eq!(table.symbols().len(), 1);
431 assert_eq!(table.symbols()[0].name, "doStuff");
432 assert_eq!(table.symbols()[0].fqn, "doStuff");
433 assert_eq!(table.symbols()[0].kind, SymbolKind::Function);
434 }
435
436 #[test]
437 fn collects_namespaced_class_with_members() {
438 let arena = bumpalo::Bump::new();
439
440 let method = MethodDecl {
442 name: "getName",
443 visibility: Some(Visibility::Public),
444 is_static: false,
445 is_abstract: false,
446 is_final: false,
447 by_ref: false,
448 params: ArenaVec::new_in(&arena),
449 return_type: None,
450 body: Some(ArenaVec::new_in(&arena)),
451 attributes: ArenaVec::new_in(&arena),
452 doc_comment: None,
453 };
454 let mut members = ArenaVec::new_in(&arena);
455 members.push(ClassMember {
456 kind: ClassMemberKind::Method(method),
457 span: Span::new(40, 60),
458 });
459
460 let class = arena.alloc(ClassDecl {
461 name: Some("User"),
462 modifiers: ClassModifiers::default(),
463 extends: None,
464 implements: ArenaVec::new_in(&arena),
465 members,
466 attributes: ArenaVec::new_in(&arena),
467 doc_comment: None,
468 });
469
470 let ns = arena.alloc(NamespaceDecl {
471 name: Some(Name::Simple {
472 value: "App",
473 span: Span::DUMMY,
474 }),
475 body: NamespaceBody::Braced({
476 let mut inner = ArenaVec::new_in(&arena);
477 inner.push(Stmt {
478 kind: StmtKind::Class(class),
479 span: Span::new(20, 80),
480 });
481 inner
482 }),
483 });
484
485 let mut stmts = ArenaVec::new_in(&arena);
486 stmts.push(Stmt {
487 kind: StmtKind::Namespace(ns),
488 span: Span::new(0, 100),
489 });
490
491 let table = build_table(&arena, stmts);
492 assert_eq!(table.symbols().len(), 2);
493 assert_eq!(table.symbols()[0].fqn, "App\\User");
494 assert_eq!(table.symbols()[0].kind, SymbolKind::Class);
495 assert_eq!(table.symbols()[1].fqn, "App\\User::getName");
496 assert_eq!(table.symbols()[1].kind, SymbolKind::Method);
497 assert_eq!(table.symbols()[1].parent.as_deref(), Some("App\\User"));
498
499 let members: Vec<_> = table.members_of("App\\User").collect();
501 assert_eq!(members.len(), 1);
502 assert_eq!(members[0].name, "getName");
503 }
504
505 #[test]
506 fn collects_use_imports() {
507 let arena = bumpalo::Bump::new();
508
509 let mut uses = ArenaVec::new_in(&arena);
510 uses.push(UseItem {
511 name: Name::Simple {
512 value: "Foo",
513 span: Span::new(4, 7),
514 },
515 alias: Some("Bar"),
516 kind: None,
517 span: Span::new(4, 7),
518 });
519
520 let use_decl = arena.alloc(UseDecl {
521 kind: UseKind::Normal,
522 uses,
523 });
524
525 let mut stmts = ArenaVec::new_in(&arena);
526 stmts.push(Stmt {
527 kind: StmtKind::Use(use_decl),
528 span: Span::new(0, 15),
529 });
530
531 let table = build_table(&arena, stmts);
532 assert_eq!(table.imports().len(), 1);
533 assert_eq!(table.imports()[0].name, "Foo");
534 assert_eq!(table.imports()[0].alias, Some("Bar"));
535 assert_eq!(table.imports()[0].local_name(), "Bar");
536 }
537
538 #[test]
539 fn resolve_name_from_imports() {
540 let arena = bumpalo::Bump::new();
541
542 let mut uses = ArenaVec::new_in(&arena);
543 uses.push(UseItem {
544 name: Name::Simple {
545 value: "User",
546 span: Span::DUMMY,
547 },
548 alias: None,
549 kind: None,
550 span: Span::DUMMY,
551 });
552
553 let use_decl = arena.alloc(UseDecl {
554 kind: UseKind::Normal,
555 uses,
556 });
557
558 let mut stmts = ArenaVec::new_in(&arena);
559 stmts.push(Stmt {
560 kind: StmtKind::Use(use_decl),
561 span: Span::DUMMY,
562 });
563
564 let table = build_table(&arena, stmts);
565 assert_eq!(table.resolve_name("User"), Some("User"));
566 assert_eq!(table.resolve_name("Unknown"), None);
567 }
568
569 #[test]
570 fn symbols_at_offset() {
571 let arena = bumpalo::Bump::new();
572 let func = arena.alloc(FunctionDecl {
573 name: "foo",
574 params: ArenaVec::new_in(&arena),
575 body: ArenaVec::new_in(&arena),
576 return_type: None,
577 by_ref: false,
578 attributes: ArenaVec::new_in(&arena),
579 doc_comment: None,
580 });
581 let mut stmts = ArenaVec::new_in(&arena);
582 stmts.push(Stmt {
583 kind: StmtKind::Function(func),
584 span: Span::new(10, 50),
585 });
586
587 let table = build_table(&arena, stmts);
588 assert_eq!(table.symbols_at(25).len(), 1);
589 assert_eq!(table.symbols_at(5).len(), 0);
590 assert_eq!(table.symbols_at(50).len(), 0);
591 }
592
593 #[test]
594 fn collects_enum_cases() {
595 let arena = bumpalo::Bump::new();
596
597 let mut members = ArenaVec::new_in(&arena);
598 members.push(EnumMember {
599 kind: EnumMemberKind::Case(EnumCase {
600 name: "Hearts",
601 value: None,
602 attributes: ArenaVec::new_in(&arena),
603 doc_comment: None,
604 }),
605 span: Span::new(30, 45),
606 });
607
608 let enum_decl = arena.alloc(EnumDecl {
609 name: "Suit",
610 scalar_type: None,
611 implements: ArenaVec::new_in(&arena),
612 members,
613 attributes: ArenaVec::new_in(&arena),
614 doc_comment: None,
615 });
616
617 let mut stmts = ArenaVec::new_in(&arena);
618 stmts.push(Stmt {
619 kind: StmtKind::Enum(enum_decl),
620 span: Span::new(0, 60),
621 });
622
623 let table = build_table(&arena, stmts);
624 assert_eq!(table.enums().count(), 1);
625 let cases: Vec<_> = table.symbols_of_kind(SymbolKind::EnumCase).collect();
626 assert_eq!(cases.len(), 1);
627 assert_eq!(cases[0].fqn, "Suit::Hearts");
628 assert_eq!(cases[0].parent.as_deref(), Some("Suit"));
629 }
630
631 #[test]
632 fn unbraced_namespace_applies_to_siblings() {
633 let arena = bumpalo::Bump::new();
634
635 let ns = arena.alloc(NamespaceDecl {
637 name: Some(Name::Complex {
638 parts: {
639 let mut v = ArenaVec::new_in(&arena);
640 v.push("App");
641 v.push("Models");
642 v
643 },
644 kind: crate::ast::NameKind::Qualified,
645 span: Span::DUMMY,
646 }),
647 body: NamespaceBody::Simple,
648 });
649
650 let class = arena.alloc(ClassDecl {
652 name: Some("User"),
653 modifiers: ClassModifiers::default(),
654 extends: None,
655 implements: ArenaVec::new_in(&arena),
656 members: ArenaVec::new_in(&arena),
657 attributes: ArenaVec::new_in(&arena),
658 doc_comment: None,
659 });
660
661 let func = arena.alloc(FunctionDecl {
663 name: "helper",
664 params: ArenaVec::new_in(&arena),
665 body: ArenaVec::new_in(&arena),
666 return_type: None,
667 by_ref: false,
668 attributes: ArenaVec::new_in(&arena),
669 doc_comment: None,
670 });
671
672 let mut stmts = ArenaVec::new_in(&arena);
673 stmts.push(Stmt {
674 kind: StmtKind::Namespace(ns),
675 span: Span::new(0, 25),
676 });
677 stmts.push(Stmt {
678 kind: StmtKind::Class(class),
679 span: Span::new(26, 50),
680 });
681 stmts.push(Stmt {
682 kind: StmtKind::Function(func),
683 span: Span::new(51, 80),
684 });
685
686 let table = build_table(&arena, stmts);
687 assert_eq!(table.symbols().len(), 2);
688 assert_eq!(table.symbols()[0].fqn, "App\\Models\\User");
689 assert_eq!(table.symbols()[1].fqn, "App\\Models\\helper");
690 }
691
692 #[test]
693 fn multiple_unbraced_namespaces_switch_context() {
694 let arena = bumpalo::Bump::new();
696
697 let ns_a = arena.alloc(NamespaceDecl {
698 name: Some(Name::Simple {
699 value: "A",
700 span: Span::DUMMY,
701 }),
702 body: NamespaceBody::Simple,
703 });
704 let class_foo = arena.alloc(ClassDecl {
705 name: Some("Foo"),
706 modifiers: ClassModifiers::default(),
707 extends: None,
708 implements: ArenaVec::new_in(&arena),
709 members: ArenaVec::new_in(&arena),
710 attributes: ArenaVec::new_in(&arena),
711 doc_comment: None,
712 });
713 let ns_b = arena.alloc(NamespaceDecl {
714 name: Some(Name::Simple {
715 value: "B",
716 span: Span::DUMMY,
717 }),
718 body: NamespaceBody::Simple,
719 });
720 let class_bar = arena.alloc(ClassDecl {
721 name: Some("Bar"),
722 modifiers: ClassModifiers::default(),
723 extends: None,
724 implements: ArenaVec::new_in(&arena),
725 members: ArenaVec::new_in(&arena),
726 attributes: ArenaVec::new_in(&arena),
727 doc_comment: None,
728 });
729
730 let mut stmts = ArenaVec::new_in(&arena);
731 stmts.push(Stmt {
732 kind: StmtKind::Namespace(ns_a),
733 span: Span::DUMMY,
734 });
735 stmts.push(Stmt {
736 kind: StmtKind::Class(class_foo),
737 span: Span::DUMMY,
738 });
739 stmts.push(Stmt {
740 kind: StmtKind::Namespace(ns_b),
741 span: Span::DUMMY,
742 });
743 stmts.push(Stmt {
744 kind: StmtKind::Class(class_bar),
745 span: Span::DUMMY,
746 });
747
748 let table = build_table(&arena, stmts);
749 assert_eq!(table.symbols().len(), 2);
750 assert_eq!(table.symbols()[0].fqn, "A\\Foo");
751 assert_eq!(table.symbols()[1].fqn, "B\\Bar");
752 }
753
754 #[test]
755 fn braced_namespace_does_not_leak_to_siblings() {
756 let arena = bumpalo::Bump::new();
758
759 let class_foo = arena.alloc(ClassDecl {
760 name: Some("Foo"),
761 modifiers: ClassModifiers::default(),
762 extends: None,
763 implements: ArenaVec::new_in(&arena),
764 members: ArenaVec::new_in(&arena),
765 attributes: ArenaVec::new_in(&arena),
766 doc_comment: None,
767 });
768 let ns = arena.alloc(NamespaceDecl {
769 name: Some(Name::Simple {
770 value: "A",
771 span: Span::DUMMY,
772 }),
773 body: NamespaceBody::Braced({
774 let mut inner = ArenaVec::new_in(&arena);
775 inner.push(Stmt {
776 kind: StmtKind::Class(class_foo),
777 span: Span::DUMMY,
778 });
779 inner
780 }),
781 });
782 let class_bar = arena.alloc(ClassDecl {
783 name: Some("Bar"),
784 modifiers: ClassModifiers::default(),
785 extends: None,
786 implements: ArenaVec::new_in(&arena),
787 members: ArenaVec::new_in(&arena),
788 attributes: ArenaVec::new_in(&arena),
789 doc_comment: None,
790 });
791
792 let mut stmts = ArenaVec::new_in(&arena);
793 stmts.push(Stmt {
794 kind: StmtKind::Namespace(ns),
795 span: Span::DUMMY,
796 });
797 stmts.push(Stmt {
798 kind: StmtKind::Class(class_bar),
799 span: Span::DUMMY,
800 });
801
802 let table = build_table(&arena, stmts);
803 assert_eq!(table.symbols().len(), 2);
804 assert_eq!(table.symbols()[0].fqn, "A\\Foo");
805 assert_eq!(table.symbols()[1].fqn, "Bar");
807 }
808
809 #[test]
810 fn unbraced_namespace_no_name_clears_namespace() {
811 let arena = bumpalo::Bump::new();
814
815 let ns_a = arena.alloc(NamespaceDecl {
816 name: Some(Name::Simple {
817 value: "A",
818 span: Span::DUMMY,
819 }),
820 body: NamespaceBody::Simple,
821 });
822 let class_foo = arena.alloc(ClassDecl {
823 name: Some("Foo"),
824 modifiers: ClassModifiers::default(),
825 extends: None,
826 implements: ArenaVec::new_in(&arena),
827 members: ArenaVec::new_in(&arena),
828 attributes: ArenaVec::new_in(&arena),
829 doc_comment: None,
830 });
831 let ns_global = arena.alloc(NamespaceDecl {
832 name: None,
833 body: NamespaceBody::Simple,
834 });
835 let class_bar = arena.alloc(ClassDecl {
836 name: Some("Bar"),
837 modifiers: ClassModifiers::default(),
838 extends: None,
839 implements: ArenaVec::new_in(&arena),
840 members: ArenaVec::new_in(&arena),
841 attributes: ArenaVec::new_in(&arena),
842 doc_comment: None,
843 });
844
845 let mut stmts = ArenaVec::new_in(&arena);
846 stmts.push(Stmt {
847 kind: StmtKind::Namespace(ns_a),
848 span: Span::DUMMY,
849 });
850 stmts.push(Stmt {
851 kind: StmtKind::Class(class_foo),
852 span: Span::DUMMY,
853 });
854 stmts.push(Stmt {
855 kind: StmtKind::Namespace(ns_global),
856 span: Span::DUMMY,
857 });
858 stmts.push(Stmt {
859 kind: StmtKind::Class(class_bar),
860 span: Span::DUMMY,
861 });
862
863 let table = build_table(&arena, stmts);
864 assert_eq!(table.symbols().len(), 2);
865 assert_eq!(table.symbols()[0].fqn, "A\\Foo");
866 assert_eq!(table.symbols()[1].fqn, "Bar");
867 }
868}