1use ryo_source::pure::{PureFile, PureItem};
36use ryo_symbol::WorkspaceFilePath;
37use slotmap::SecondaryMap;
38use std::collections::HashSet;
39use std::sync::Arc;
40
41use crate::symbol::{SymbolId, SymbolPath, SymbolRegistry};
42
43#[derive(Debug, Clone, Default)]
58pub struct ASTRegistry {
59 items: SecondaryMap<SymbolId, PureItem>,
64 module_children: SecondaryMap<SymbolId, Vec<SymbolId>>,
68 inline_modules: HashSet<SymbolId>,
77}
78
79impl ASTRegistry {
80 pub fn new() -> Self {
82 Self {
83 items: SecondaryMap::new(),
84 module_children: SecondaryMap::new(),
85 inline_modules: HashSet::new(),
86 }
87 }
88
89 pub fn get(&self, id: SymbolId) -> Option<&PureItem> {
91 self.items.get(id)
92 }
93
94 pub fn get_mut(&mut self, id: SymbolId) -> Option<&mut PureItem> {
96 self.items.get_mut(id)
97 }
98
99 pub fn set(&mut self, id: SymbolId, item: PureItem) {
103 self.items.insert(id, item);
104 }
105
106 pub fn remove(&mut self, id: SymbolId) -> Option<PureItem> {
110 self.items.remove(id)
111 }
112
113 pub fn contains(&self, id: SymbolId) -> bool {
115 self.items.contains_key(id)
116 }
117
118 pub fn len(&self) -> usize {
120 self.items.len()
121 }
122
123 pub fn is_empty(&self) -> bool {
125 self.items.is_empty()
126 }
127
128 pub fn iter(&self) -> impl Iterator<Item = (SymbolId, &PureItem)> {
130 self.items.iter()
131 }
132
133 pub fn iter_mut(&mut self) -> impl Iterator<Item = (SymbolId, &mut PureItem)> {
135 self.items.iter_mut()
136 }
137
138 pub fn clear(&mut self) {
140 self.items.clear();
141 self.module_children.clear();
142 self.inline_modules.clear();
143 }
144
145 pub fn mark_inline_module(&mut self, id: SymbolId) {
152 self.inline_modules.insert(id);
153 }
154
155 pub fn is_inline_module(&self, id: SymbolId) -> bool {
157 self.inline_modules.contains(&id)
158 }
159
160 pub fn inline_module_ids(&self) -> impl Iterator<Item = SymbolId> + '_ {
162 self.inline_modules.iter().copied()
163 }
164
165 pub fn get_module_children(&self, module_id: SymbolId) -> Option<&Vec<SymbolId>> {
169 self.module_children.get(module_id)
170 }
171
172 pub fn get_module_children_mut(&mut self, module_id: SymbolId) -> Option<&mut Vec<SymbolId>> {
174 self.module_children.get_mut(module_id)
175 }
176
177 pub fn set_module_children(&mut self, module_id: SymbolId, children: Vec<SymbolId>) {
179 self.module_children.insert(module_id, children);
180 }
181
182 pub fn add_child_to_module(&mut self, module_id: SymbolId, child_id: SymbolId) {
188 self.module_children
189 .entry(module_id)
190 .expect("caller must supply a valid SymbolId registered in the SlotMap")
191 .or_default()
192 .push(child_id);
193 }
194
195 pub fn remove_child_from_module(&mut self, module_id: SymbolId, child_id: SymbolId) {
197 if let Some(children) = self.module_children.get_mut(module_id) {
198 children.retain(|&id| id != child_id);
199 }
200 }
201
202 pub fn has_module_children(&self, module_id: SymbolId) -> bool {
204 self.module_children.contains_key(module_id)
205 }
206
207 pub fn iter_module_children(&self) -> impl Iterator<Item = (SymbolId, &Vec<SymbolId>)> {
209 self.module_children.iter()
210 }
211
212 pub fn get_module_items(&self, module_id: SymbolId) -> Option<&Vec<PureItem>> {
217 match self.items.get(module_id) {
218 Some(PureItem::Mod(m)) => Some(&m.items),
219 _ => None,
220 }
221 }
222
223 pub fn get_module_items_mut(&mut self, module_id: SymbolId) -> Option<&mut Vec<PureItem>> {
225 match self.items.get_mut(module_id) {
226 Some(PureItem::Mod(m)) => Some(&mut m.items),
227 _ => None,
228 }
229 }
230
231 pub fn set_module_items(&mut self, module_id: SymbolId, items: Vec<PureItem>) {
238 use ryo_source::pure::{PureMod, PureVis};
239 match self.items.get_mut(module_id) {
240 Some(PureItem::Mod(m)) => {
241 m.items = items;
242 }
243 _ => {
244 if items.is_empty() {
249 return;
250 }
251 self.items.insert(
255 module_id,
256 PureItem::Mod(PureMod {
257 attrs: vec![],
258 vis: PureVis::Public, name: String::new(), items,
261 }),
262 );
263 }
264 }
265 }
266
267 pub fn has_module_items(&self, module_id: SymbolId) -> bool {
269 match self.items.get(module_id) {
270 Some(PureItem::Mod(m)) => !m.items.is_empty(),
271 _ => false,
272 }
273 }
274
275 pub fn iter_module_items(&self) -> impl Iterator<Item = (SymbolId, &Vec<PureItem>)> {
277 self.items.iter().filter_map(|(id, item)| {
278 if let PureItem::Mod(m) = item {
279 Some((id, &m.items))
280 } else {
281 None
282 }
283 })
284 }
285
286 pub fn iter_module_items_mut(
288 &mut self,
289 ) -> impl Iterator<Item = (SymbolId, &mut Vec<PureItem>)> {
290 self.items.iter_mut().filter_map(|(id, item)| {
291 if let PureItem::Mod(m) = item {
292 Some((id, &mut m.items))
293 } else {
294 None
295 }
296 })
297 }
298
299 pub fn build_from_files(
310 files: &im::HashMap<WorkspaceFilePath, Arc<PureFile>>,
311 registry: &SymbolRegistry,
312 _crate_name: &str,
313 ) -> Self {
314 use ryo_symbol::SymbolPathResolver;
315
316 let mut ast_registry = Self::new();
317
318 for (file_path, file) in files {
319 let file_crate_name = file_path.crate_name().as_str();
321 let resolver = SymbolPathResolver::new(file_crate_name);
322 let module_path_str = resolver.module_path_str(file_path);
323 let module_path = match SymbolPath::parse(&module_path_str) {
324 Ok(p) => p,
325 Err(_) => continue,
326 };
327
328 ast_registry.store_items_from_file(&module_path, file.as_ref(), registry);
329 }
330
331 ast_registry
332 }
333
334 fn store_items_from_file(
344 &mut self,
345 module_path: &SymbolPath,
346 file: &PureFile,
347 registry: &SymbolRegistry,
348 ) {
349 self.store_items_recursive(module_path, &file.items, registry);
350 }
351
352 fn store_items_recursive(
354 &mut self,
355 module_path: &SymbolPath,
356 items: &[PureItem],
357 registry: &SymbolRegistry,
358 ) {
359 use ryo_source::pure::PureImpl;
360 use std::collections::HashMap;
361
362 let mut impl_map: HashMap<(String, Option<String>), PureImpl> = HashMap::new();
364 let mut merged_items: Vec<PureItem> = Vec::new();
365
366 for item in items {
367 if let PureItem::Impl(impl_block) = item {
368 let key = (impl_block.self_ty.clone(), impl_block.trait_.clone());
369 if let Some(existing) = impl_map.get_mut(&key) {
370 existing.items.extend(impl_block.items.clone());
372 } else {
373 impl_map.insert(key, impl_block.clone());
374 }
375 } else {
376 merged_items.push(item.clone());
377 }
378 }
379
380 for impl_block in impl_map.into_values() {
382 merged_items.push(PureItem::Impl(impl_block));
383 }
384
385 let mut child_ids: Vec<SymbolId> = Vec::new();
387
388 for item in &merged_items {
390 if let PureItem::Impl(impl_block) = item {
397 use ryo_source::pure::PureImplItem;
398
399 let (impl_path_str, method_base_path) =
401 if let Some(ref trait_name) = &impl_block.trait_ {
402 let impl_path = format!(
404 "{}::<impl {} for {}>",
405 module_path, trait_name, impl_block.self_ty
406 );
407 (impl_path.clone(), impl_path)
408 } else {
409 let impl_path = format!("{}::<impl {}>", module_path, impl_block.self_ty);
411 let base_type = impl_block
415 .self_ty
416 .split('<')
417 .next()
418 .unwrap_or(&impl_block.self_ty)
419 .trim();
420 let method_path = format!("{}::{}", module_path, base_type);
421 (impl_path, method_path)
422 };
423
424 if let Ok(impl_path) = SymbolPath::parse(&impl_path_str) {
426 if let Some(id) = registry.lookup(&impl_path) {
427 self.set(id, item.clone());
428 child_ids.push(id);
429 }
430 }
431
432 for impl_item in &impl_block.items {
434 let (item_name, pure_item) = match impl_item {
435 PureImplItem::Fn(m) => (m.name.clone(), PureItem::Fn(m.clone())),
436 PureImplItem::Const(c) => (c.name.clone(), PureItem::Const(c.clone())),
437 PureImplItem::Type(t) => (t.name.clone(), PureItem::Type(t.clone())),
438 PureImplItem::Other(_) => continue,
439 };
440
441 let item_path_str = format!("{}::{}", method_base_path, item_name);
442 if let Ok(item_path) = SymbolPath::parse(&item_path_str) {
443 if let Some(id) = registry.lookup(&item_path) {
444 self.set(id, pure_item);
445 }
447 }
448 }
449 continue;
450 }
451
452 if let PureItem::Mod(m) = item {
454 if m.items.is_empty() {
455 continue;
458 }
459
460 let mod_path = match module_path.child(&m.name) {
462 Ok(p) => p,
463 Err(_) => continue,
464 };
465
466 if let Some(id) = registry.lookup(&mod_path) {
467 child_ids.push(id);
468
469 self.mark_inline_module(id);
472
473 self.store_items_recursive(&mod_path, &m.items, registry);
476
477 self.set(id, item.clone());
480 }
481 continue;
482 }
483
484 let name = match item_name(item) {
485 Some(n) => n,
486 None => continue,
487 };
488
489 let item_path = match module_path.child(&name) {
491 Ok(p) => p,
492 Err(_) => continue,
493 };
494
495 if let Some(id) = registry.lookup(&item_path) {
497 self.set(id, item.clone());
498 child_ids.push(id);
499 }
500 }
501
502 if let Some(module_id) = registry.lookup(module_path) {
507 let vis = registry
509 .visibility(module_id)
510 .map(|v| match v {
511 ryo_symbol::Visibility::Public => ryo_source::pure::PureVis::Public,
512 _ => ryo_source::pure::PureVis::Private,
513 })
514 .unwrap_or_default();
515
516 let pure_mod = ryo_source::pure::PureMod {
518 attrs: vec![],
519 vis,
520 name: module_path
521 .segment_refs()
522 .last()
523 .map(|s| s.name().to_string())
524 .unwrap_or_default(),
525 items: merged_items,
526 };
527 self.set(module_id, PureItem::Mod(pure_mod));
528 self.set_module_children(module_id, child_ids);
529 }
530 }
531}
532
533fn item_name(item: &PureItem) -> Option<String> {
535 match item {
536 PureItem::Struct(s) => Some(s.name.clone()),
537 PureItem::Enum(e) => Some(e.name.clone()),
538 PureItem::Fn(f) => Some(f.name.clone()),
539 PureItem::Mod(m) => Some(m.name.clone()),
540 PureItem::Trait(t) => Some(t.name.clone()),
541 PureItem::Type(t) => Some(t.name.clone()),
542 PureItem::Const(c) => Some(c.name.clone()),
543 PureItem::Static(s) => Some(s.name.clone()),
544 PureItem::Impl(i) => {
545 let impl_suffix = match &i.trait_ {
547 Some(trait_name) => format!("impl_{}", trait_name.replace("::", "_")),
548 None => "impl".to_string(),
549 };
550 Some(format!("{}::{}", i.self_ty, impl_suffix))
552 }
553 PureItem::Use(_) | PureItem::Macro(_) | PureItem::Other(_) => None,
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use ryo_source::pure::{PureFields, PureStruct, PureVis};
561 use slotmap::SlotMap;
562
563 fn make_symbol_id() -> SymbolId {
564 let mut sm: SlotMap<SymbolId, ()> = SlotMap::with_key();
565 sm.insert(())
566 }
567
568 #[test]
569 fn test_inline_module_recursive() {
570 use ryo_source::pure::PureFile;
571
572 let source = r#"
573pub struct Config {
574 pub name: String,
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580
581 #[test]
582 fn test_config() {
583 let c = Config { name: "test".to_string() };
584 assert_eq!(c.name, "test");
585 }
586}
587"#;
588
589 let file = PureFile::from_source(source).unwrap();
590
591 assert_eq!(file.items.len(), 2, "Should have Config and tests module");
593
594 let tests_mod = file.items.iter().find_map(|item| {
596 if let PureItem::Mod(m) = item {
597 if m.name == "tests" {
598 return Some(m);
599 }
600 }
601 None
602 });
603
604 let tests_mod = tests_mod.expect("Should have tests module");
605
606 assert!(
608 !tests_mod.items.is_empty(),
609 "tests module should have items"
610 );
611
612 let has_fn = tests_mod
614 .items
615 .iter()
616 .any(|item| matches!(item, PureItem::Fn(f) if f.name == "test_config"));
617
618 assert!(
619 has_fn,
620 "tests module should have test_config function, got: {:?}",
621 tests_mod
622 .items
623 .iter()
624 .map(std::mem::discriminant)
625 .collect::<Vec<_>>()
626 );
627 }
628
629 #[test]
631 fn test_cfg_test_attr_preserved_in_registry() {
632 use crate::SymbolKind;
633 use ryo_source::pure::{PureAttrMeta, PureFile};
634
635 let source = r#"
636pub struct Config {
637 pub name: String,
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643
644 #[test]
645 fn test_config() {
646 let c = Config { name: "test".to_string() };
647 assert_eq!(c.name, "test");
648 }
649}
650"#;
651
652 let file = PureFile::from_source(source).unwrap();
653
654 let mut symbol_registry = SymbolRegistry::new();
656
657 symbol_registry
658 .register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
659 .unwrap();
660 symbol_registry
661 .register(
662 SymbolPath::parse("my_crate::Config").unwrap(),
663 SymbolKind::Struct,
664 )
665 .unwrap();
666 symbol_registry
667 .register(
668 SymbolPath::parse("my_crate::tests").unwrap(),
669 SymbolKind::Mod,
670 )
671 .unwrap();
672 symbol_registry
673 .register(
674 SymbolPath::parse("my_crate::tests::test_config").unwrap(),
675 SymbolKind::Function,
676 )
677 .unwrap();
678
679 let mut ast_registry = ASTRegistry::new();
681 let module_path = SymbolPath::parse("my_crate").unwrap();
682 ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
683
684 let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
686 let tests_id = symbol_registry
687 .lookup(&tests_path)
688 .expect("tests should exist");
689
690 let tests_item = ast_registry
692 .get(tests_id)
693 .expect("tests AST should be registered");
694 if let PureItem::Mod(m) = tests_item {
695 let has_cfg_test = m.attrs.iter().any(|attr| {
697 attr.path == "cfg" && matches!(&attr.meta, PureAttrMeta::List(s) if s == "test")
698 });
699 assert!(
700 has_cfg_test,
701 "tests module should have #[cfg(test)] attribute, got attrs: {:?}",
702 m.attrs
703 );
704 } else {
705 panic!("tests should be a module, got: {:?}", tests_item);
706 }
707
708 assert!(
710 ast_registry.is_inline_module(tests_id),
711 "tests should be marked as inline module"
712 );
713
714 let test_config_path = SymbolPath::parse("my_crate::tests::test_config").unwrap();
716 let test_config_id = symbol_registry
717 .lookup(&test_config_path)
718 .expect("test_config should exist");
719 assert!(
720 ast_registry.contains(test_config_id),
721 "test_config AST should be registered"
722 );
723
724 if let Some(PureItem::Fn(f)) = ast_registry.get(test_config_id) {
726 let has_test_attr = f.attrs.iter().any(|attr| attr.path == "test");
727 assert!(
728 has_test_attr,
729 "test_config should have #[test] attribute, got attrs: {:?}",
730 f.attrs
731 );
732 } else {
733 panic!(
734 "test_config should be a function, got: {:?}",
735 ast_registry.get(test_config_id)
736 );
737 }
738
739 let module_items = ast_registry
741 .get_module_items(tests_id)
742 .expect("tests module should have items");
743
744 let has_use = module_items
746 .iter()
747 .any(|item| matches!(item, PureItem::Use(_)));
748 assert!(
749 has_use,
750 "tests module items should contain use statement, got: {:?}",
751 module_items.iter().map(item_name).collect::<Vec<_>>()
752 );
753
754 let has_fn = module_items
755 .iter()
756 .any(|item| matches!(item, PureItem::Fn(f) if f.name == "test_config"));
757 assert!(
758 has_fn,
759 "tests module items should contain test_config function"
760 );
761 }
762
763 #[test]
765 fn test_nested_inline_test_modules() {
766 use crate::SymbolKind;
767 use ryo_source::pure::{PureAttrMeta, PureFile};
768
769 let source = r#"
770pub struct Outer;
771
772#[cfg(test)]
773mod tests {
774 use super::*;
775
776 mod nested {
777 use super::*;
778
779 #[test]
780 fn test_nested() {}
781 }
782
783 #[test]
784 fn test_outer() {}
785}
786"#;
787
788 let file = PureFile::from_source(source).unwrap();
789
790 let mut symbol_registry = SymbolRegistry::new();
791
792 symbol_registry
793 .register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
794 .unwrap();
795 symbol_registry
796 .register(
797 SymbolPath::parse("my_crate::Outer").unwrap(),
798 SymbolKind::Struct,
799 )
800 .unwrap();
801 symbol_registry
802 .register(
803 SymbolPath::parse("my_crate::tests").unwrap(),
804 SymbolKind::Mod,
805 )
806 .unwrap();
807 symbol_registry
808 .register(
809 SymbolPath::parse("my_crate::tests::nested").unwrap(),
810 SymbolKind::Mod,
811 )
812 .unwrap();
813 symbol_registry
814 .register(
815 SymbolPath::parse("my_crate::tests::test_outer").unwrap(),
816 SymbolKind::Function,
817 )
818 .unwrap();
819 symbol_registry
820 .register(
821 SymbolPath::parse("my_crate::tests::nested::test_nested").unwrap(),
822 SymbolKind::Function,
823 )
824 .unwrap();
825
826 let mut ast_registry = ASTRegistry::new();
827 let module_path = SymbolPath::parse("my_crate").unwrap();
828 ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
829
830 let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
832 let tests_id = symbol_registry.lookup(&tests_path).unwrap();
833
834 if let Some(PureItem::Mod(m)) = ast_registry.get(tests_id) {
835 let has_cfg_test = m.attrs.iter().any(|attr| {
836 attr.path == "cfg" && matches!(&attr.meta, PureAttrMeta::List(s) if s == "test")
837 });
838 assert!(
839 has_cfg_test,
840 "tests module should have #[cfg(test)], got: {:?}",
841 m.attrs
842 );
843 } else {
844 panic!("tests should be a module");
845 }
846
847 assert!(ast_registry.is_inline_module(tests_id));
849
850 let nested_path = SymbolPath::parse("my_crate::tests::nested").unwrap();
851 let nested_id = symbol_registry.lookup(&nested_path).unwrap();
852 assert!(ast_registry.is_inline_module(nested_id));
853
854 let nested_fn_path = SymbolPath::parse("my_crate::tests::nested::test_nested").unwrap();
856 let nested_fn_id = symbol_registry.lookup(&nested_fn_path).unwrap();
857 assert!(
858 ast_registry.contains(nested_fn_id),
859 "nested test function should be registered"
860 );
861
862 let tests_children = ast_registry.get_module_children(tests_id).unwrap();
864 assert!(
865 tests_children.contains(&nested_id),
866 "tests children should contain nested module"
867 );
868 }
869
870 #[test]
872 fn test_inline_test_module_with_impl() {
873 use crate::SymbolKind;
874 use ryo_source::pure::{PureAttrMeta, PureFile};
875
876 let source = r#"
877pub struct Config {
878 pub name: String,
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884
885 struct TestHelper {
886 value: i32,
887 }
888
889 impl TestHelper {
890 fn new(value: i32) -> Self {
891 Self { value }
892 }
893 }
894
895 #[test]
896 fn test_with_helper() {
897 let helper = TestHelper::new(42);
898 assert_eq!(helper.value, 42);
899 }
900}
901"#;
902
903 let file = PureFile::from_source(source).unwrap();
904
905 let mut symbol_registry = SymbolRegistry::new();
906
907 symbol_registry
908 .register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
909 .unwrap();
910 symbol_registry
911 .register(
912 SymbolPath::parse("my_crate::Config").unwrap(),
913 SymbolKind::Struct,
914 )
915 .unwrap();
916 symbol_registry
917 .register(
918 SymbolPath::parse("my_crate::tests").unwrap(),
919 SymbolKind::Mod,
920 )
921 .unwrap();
922 symbol_registry
923 .register(
924 SymbolPath::parse("my_crate::tests::TestHelper").unwrap(),
925 SymbolKind::Struct,
926 )
927 .unwrap();
928 symbol_registry
929 .register(
930 SymbolPath::parse("my_crate::tests::<impl TestHelper>").unwrap(),
931 SymbolKind::Impl,
932 )
933 .unwrap();
934 symbol_registry
935 .register(
936 SymbolPath::parse("my_crate::tests::TestHelper::new").unwrap(),
937 SymbolKind::Function,
938 )
939 .unwrap();
940 symbol_registry
941 .register(
942 SymbolPath::parse("my_crate::tests::test_with_helper").unwrap(),
943 SymbolKind::Function,
944 )
945 .unwrap();
946
947 let mut ast_registry = ASTRegistry::new();
948 let module_path = SymbolPath::parse("my_crate").unwrap();
949 ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
950
951 let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
953 let tests_id = symbol_registry.lookup(&tests_path).unwrap();
954
955 if let Some(PureItem::Mod(m)) = ast_registry.get(tests_id) {
956 let has_cfg_test = m.attrs.iter().any(|attr| {
957 attr.path == "cfg" && matches!(&attr.meta, PureAttrMeta::List(s) if s == "test")
958 });
959 assert!(
960 has_cfg_test,
961 "tests module should have #[cfg(test)], got: {:?}",
962 m.attrs
963 );
964 } else {
965 panic!("tests should be a module");
966 }
967
968 let helper_path = SymbolPath::parse("my_crate::tests::TestHelper").unwrap();
970 let helper_id = symbol_registry.lookup(&helper_path).unwrap();
971 assert!(
972 ast_registry.contains(helper_id),
973 "TestHelper struct should be registered"
974 );
975
976 let impl_path = SymbolPath::parse("my_crate::tests::<impl TestHelper>").unwrap();
978 let impl_id = symbol_registry.lookup(&impl_path).unwrap();
979 assert!(
980 ast_registry.contains(impl_id),
981 "impl TestHelper should be registered"
982 );
983
984 let new_path = SymbolPath::parse("my_crate::tests::TestHelper::new").unwrap();
986 let new_id = symbol_registry.lookup(&new_path).unwrap();
987 assert!(
988 ast_registry.contains(new_id),
989 "TestHelper::new method should be registered"
990 );
991
992 let module_items = ast_registry
994 .get_module_items(tests_id)
995 .expect("tests module should have items");
996
997 assert!(
999 module_items
1000 .iter()
1001 .any(|item| matches!(item, PureItem::Use(_))),
1002 "module_items should contain use statement"
1003 );
1004 assert!(
1005 module_items
1006 .iter()
1007 .any(|item| matches!(item, PureItem::Struct(s) if s.name == "TestHelper")),
1008 "module_items should contain TestHelper struct"
1009 );
1010 assert!(
1011 module_items
1012 .iter()
1013 .any(|item| matches!(item, PureItem::Impl(i) if i.self_ty == "TestHelper")),
1014 "module_items should contain impl TestHelper"
1015 );
1016 assert!(
1017 module_items
1018 .iter()
1019 .any(|item| matches!(item, PureItem::Fn(f) if f.name == "test_with_helper")),
1020 "module_items should contain test_with_helper function"
1021 );
1022 }
1023
1024 #[test]
1025 fn test_store_items_recursive_with_registry() {
1026 use crate::SymbolKind;
1027 use ryo_source::pure::PureFile;
1028
1029 let source = r#"
1030pub struct Config {
1031 pub name: String,
1032}
1033
1034mod tests {
1035 fn test_config() {}
1036}
1037"#;
1038
1039 let file = PureFile::from_source(source).unwrap();
1040
1041 let mut symbol_registry = SymbolRegistry::new();
1043
1044 symbol_registry
1046 .register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
1047 .unwrap();
1048 symbol_registry
1049 .register(
1050 SymbolPath::parse("my_crate::Config").unwrap(),
1051 SymbolKind::Struct,
1052 )
1053 .unwrap();
1054 symbol_registry
1055 .register(
1056 SymbolPath::parse("my_crate::tests").unwrap(),
1057 SymbolKind::Mod,
1058 )
1059 .unwrap();
1060 symbol_registry
1061 .register(
1062 SymbolPath::parse("my_crate::tests::test_config").unwrap(),
1063 SymbolKind::Function,
1064 )
1065 .unwrap();
1066
1067 let mut ast_registry = ASTRegistry::new();
1069 let module_path = SymbolPath::parse("my_crate").unwrap();
1070 ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
1071
1072 let config_path = SymbolPath::parse("my_crate::Config").unwrap();
1074 let config_id = symbol_registry
1075 .lookup(&config_path)
1076 .expect("Config should exist");
1077 assert!(
1078 ast_registry.contains(config_id),
1079 "Config AST should be registered"
1080 );
1081
1082 let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
1084 let tests_id = symbol_registry
1085 .lookup(&tests_path)
1086 .expect("tests should exist");
1087 assert!(
1088 ast_registry.contains(tests_id),
1089 "tests AST should be registered"
1090 );
1091
1092 let test_config_path = SymbolPath::parse("my_crate::tests::test_config").unwrap();
1094 let test_config_id = symbol_registry
1095 .lookup(&test_config_path)
1096 .expect("test_config should exist");
1097 assert!(
1098 ast_registry.contains(test_config_id),
1099 "test_config AST should be registered via recursive processing"
1100 );
1101
1102 if let Some(PureItem::Fn(f)) = ast_registry.get(test_config_id) {
1104 assert_eq!(f.name, "test_config");
1105 } else {
1106 panic!(
1107 "test_config should be a function, got: {:?}",
1108 ast_registry.get(test_config_id)
1109 );
1110 }
1111 }
1112
1113 #[test]
1114 fn test_basic_operations() {
1115 let mut registry = ASTRegistry::new();
1116 let id = make_symbol_id();
1117
1118 assert!(registry.is_empty());
1120 assert!(!registry.contains(id));
1121 assert!(registry.get(id).is_none());
1122
1123 let item = PureItem::Struct(PureStruct {
1125 attrs: vec![],
1126 vis: PureVis::Public,
1127 name: "TestStruct".to_string(),
1128 generics: Default::default(),
1129 fields: PureFields::Unit,
1130 });
1131 registry.set(id, item);
1132
1133 assert!(!registry.is_empty());
1135 assert!(registry.contains(id));
1136 assert!(registry.get(id).is_some());
1137 assert_eq!(registry.len(), 1);
1138
1139 if let Some(PureItem::Struct(s)) = registry.get_mut(id) {
1141 s.name = "ModifiedStruct".to_string();
1142 }
1143
1144 if let Some(PureItem::Struct(s)) = registry.get(id) {
1146 assert_eq!(s.name, "ModifiedStruct");
1147 } else {
1148 panic!("Expected struct");
1149 }
1150
1151 let removed = registry.remove(id);
1153 assert!(removed.is_some());
1154 assert!(registry.is_empty());
1155 }
1156
1157 #[test]
1158 fn test_iteration() {
1159 let mut registry = ASTRegistry::new();
1160 let mut sm: SlotMap<SymbolId, ()> = SlotMap::with_key();
1161
1162 let id1 = sm.insert(());
1163 let id2 = sm.insert(());
1164
1165 registry.set(
1166 id1,
1167 PureItem::Struct(PureStruct {
1168 attrs: vec![],
1169 vis: PureVis::Public,
1170 name: "Struct1".to_string(),
1171 generics: Default::default(),
1172 fields: PureFields::Unit,
1173 }),
1174 );
1175
1176 registry.set(
1177 id2,
1178 PureItem::Struct(PureStruct {
1179 attrs: vec![],
1180 vis: PureVis::Private,
1181 name: "Struct2".to_string(),
1182 generics: Default::default(),
1183 fields: PureFields::Unit,
1184 }),
1185 );
1186
1187 let items: Vec<_> = registry.iter().collect();
1188 assert_eq!(items.len(), 2);
1189 }
1190
1191 #[test]
1192 fn test_is_binary_entry() {
1193 let main_rs = WorkspaceFilePath::new_for_test("src/main.rs", "/workspace", "my_crate");
1195 assert!(main_rs.is_binary_entry());
1196
1197 let lib_rs = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "my_crate");
1199 assert!(!lib_rs.is_binary_entry());
1200
1201 let bin_foo = WorkspaceFilePath::new_for_test("src/bin/foo.rs", "/workspace", "my_crate");
1203 assert!(bin_foo.is_binary_entry());
1204
1205 let module = WorkspaceFilePath::new_for_test("src/utils/mod.rs", "/workspace", "my_crate");
1207 assert!(!module.is_binary_entry());
1208
1209 let workspace_main =
1211 WorkspaceFilePath::new_for_test("crates/my_app/src/main.rs", "/workspace", "my_app");
1212 assert!(workspace_main.is_binary_entry());
1213 }
1214}