1use std::collections::HashMap;
47
48use slotmap::{SecondaryMap, SlotMap};
49use uuid::Uuid;
50
51use crate::error::{InvalidSymbolId, RegistrationError, RenameError, UnregisterReexportError};
52use crate::file_path::WorkspaceFilePath;
53use crate::id::SymbolId;
54use crate::kind::SymbolKind;
55use crate::path::SymbolPath;
56use crate::span::{FileSpan, Visibility};
57use crate::var_scope::VarScope;
58
59#[derive(Debug, Clone)]
61pub struct ReExportInfo {
62 pub alias_path: SymbolPath,
64 pub origin_file: WorkspaceFilePath,
66}
67
68#[derive(Clone)]
88pub struct SymbolRegistry {
89 id_to_path: SlotMap<SymbolId, SymbolPath>,
92 path_to_id: HashMap<SymbolPath, SymbolId>,
94
95 kinds: SecondaryMap<SymbolId, SymbolKind>,
98 spans: SecondaryMap<SymbolId, FileSpan>,
100 visibility: SecondaryMap<SymbolId, Visibility>,
102 parents: SecondaryMap<SymbolId, SymbolId>,
104
105 re_exports: SecondaryMap<SymbolId, Vec<ReExportInfo>>,
108 alias_to_canonical: HashMap<SymbolPath, SymbolId>,
110
111 id_to_uuid: SecondaryMap<SymbolId, Uuid>,
114 uuid_to_id: HashMap<Uuid, SymbolId>,
116 preloaded_uuids: HashMap<SymbolPath, Uuid>,
119}
120
121impl SymbolRegistry {
122 pub fn new() -> Self {
124 Self {
125 id_to_path: SlotMap::with_key(),
126 path_to_id: HashMap::new(),
127 kinds: SecondaryMap::new(),
128 spans: SecondaryMap::new(),
129 visibility: SecondaryMap::new(),
130 parents: SecondaryMap::new(),
131 re_exports: SecondaryMap::new(),
132 alias_to_canonical: HashMap::new(),
133 id_to_uuid: SecondaryMap::new(),
134 uuid_to_id: HashMap::new(),
135 preloaded_uuids: HashMap::new(),
136 }
137 }
138
139 pub fn with_capacity(capacity: usize) -> Self {
141 Self {
142 id_to_path: SlotMap::with_capacity_and_key(capacity),
143 path_to_id: HashMap::with_capacity(capacity),
144 kinds: SecondaryMap::new(),
145 spans: SecondaryMap::new(),
146 visibility: SecondaryMap::new(),
147 parents: SecondaryMap::new(),
148 re_exports: SecondaryMap::new(),
149 alias_to_canonical: HashMap::new(),
150 id_to_uuid: SecondaryMap::new(),
151 uuid_to_id: HashMap::with_capacity(capacity),
152 preloaded_uuids: HashMap::new(),
153 }
154 }
155
156 pub fn register(
164 &mut self,
165 path: SymbolPath,
166 kind: SymbolKind,
167 ) -> Result<SymbolId, RegistrationError> {
168 if let Some(&existing_id) = self.path_to_id.get(&path) {
170 if let Some(&existing_kind) = self.kinds.get(existing_id) {
172 if existing_kind != kind {
173 return Err(RegistrationError::ConflictingKind {
174 path: Box::new(path),
175 existing: existing_kind,
176 new: kind,
177 });
178 }
179 }
180 return Ok(existing_id);
181 }
182
183 let id = self.id_to_path.insert(path.clone());
185 self.path_to_id.insert(path.clone(), id);
186 self.kinds.insert(id, kind);
187
188 let uuid = self
190 .preloaded_uuids
191 .remove(&path)
192 .unwrap_or_else(Uuid::new_v4);
193 self.id_to_uuid.insert(id, uuid);
194 self.uuid_to_id.insert(uuid, id);
195
196 Ok(id)
197 }
198
199 pub fn register_with_metadata(
201 &mut self,
202 path: SymbolPath,
203 kind: SymbolKind,
204 span: Option<FileSpan>,
205 vis: Option<Visibility>,
206 ) -> Result<SymbolId, RegistrationError> {
207 let id = self.register(path, kind)?;
208
209 if let Some(span) = span {
210 self.spans.insert(id, span);
211 }
212 if let Some(vis) = vis {
213 self.visibility.insert(id, vis);
214 }
215
216 Ok(id)
217 }
218
219 pub fn register_var(
229 &mut self,
230 containing_symbol: SymbolId,
231 scope: VarScope,
232 name: &str,
233 kind: SymbolKind,
234 ) -> Result<SymbolId, RegistrationError> {
235 let parent_path = self
237 .path(containing_symbol)
238 .ok_or(RegistrationError::InvalidParent)?
239 .clone();
240
241 let var_path = parent_path
243 .with_var_scope(scope, name)
244 .map_err(RegistrationError::InvalidPath)?;
245
246 let id = self.register(var_path, kind)?;
248
249 self.parents.insert(id, containing_symbol);
251
252 Ok(id)
253 }
254
255 #[inline]
261 pub fn lookup(&self, path: &SymbolPath) -> Option<SymbolId> {
262 if let Some(&id) = self.path_to_id.get(path) {
264 return Some(id);
265 }
266 self.alias_to_canonical.get(path).copied()
268 }
269
270 #[inline]
272 pub fn resolve(&self, id: SymbolId) -> Option<&SymbolPath> {
273 self.id_to_path.get(id)
274 }
275
276 #[inline]
278 pub fn path(&self, id: SymbolId) -> Option<&SymbolPath> {
279 self.resolve(id)
280 }
281
282 #[inline]
293 pub fn get_ref(&self, id: SymbolId) -> Option<crate::SymbolRef> {
294 self.resolve(id)
295 .map(|path| crate::SymbolRef::new(id, path.clone()))
296 }
297
298 #[inline]
300 pub fn contains(&self, id: SymbolId) -> bool {
301 self.id_to_path.contains_key(id)
302 }
303
304 #[inline]
308 pub fn kind(&self, id: SymbolId) -> Option<SymbolKind> {
309 self.kinds.get(id).copied()
310 }
311
312 #[inline]
314 pub fn span(&self, id: SymbolId) -> Option<&FileSpan> {
315 self.spans.get(id)
316 }
317
318 #[inline]
320 pub fn visibility(&self, id: SymbolId) -> Option<&Visibility> {
321 self.visibility.get(id)
322 }
323
324 #[inline]
326 pub fn parent(&self, id: SymbolId) -> Option<SymbolId> {
327 self.parents.get(id).copied()
328 }
329
330 pub fn set_span(&mut self, id: SymbolId, span: FileSpan) -> Result<(), InvalidSymbolId> {
334 if !self.contains(id) {
335 return Err(InvalidSymbolId(id));
336 }
337 self.spans.insert(id, span);
338 Ok(())
339 }
340
341 pub fn set_visibility(&mut self, id: SymbolId, vis: Visibility) -> Result<(), InvalidSymbolId> {
343 if !self.contains(id) {
344 return Err(InvalidSymbolId(id));
345 }
346 self.visibility.insert(id, vis);
347 Ok(())
348 }
349
350 pub fn set_kind(&mut self, id: SymbolId, kind: SymbolKind) -> Result<(), InvalidSymbolId> {
352 if !self.contains(id) {
353 return Err(InvalidSymbolId(id));
354 }
355 self.kinds.insert(id, kind);
356 Ok(())
357 }
358
359 pub fn remove(&mut self, id: SymbolId) -> Option<SymbolPath> {
364 let path = self.id_to_path.remove(id)?;
365 self.path_to_id.remove(&path);
366 self.kinds.remove(id);
367 self.spans.remove(id);
368 self.visibility.remove(id);
369 self.parents.remove(id);
370
371 if let Some(aliases) = self.re_exports.remove(id) {
373 for info in aliases {
374 self.alias_to_canonical.remove(&info.alias_path);
375 }
376 }
377
378 if let Some(uuid) = self.id_to_uuid.remove(id) {
380 self.uuid_to_id.remove(&uuid);
381 }
382
383 Some(path)
384 }
385
386 pub fn rename(
390 &mut self,
391 id: SymbolId,
392 new_path: SymbolPath,
393 ) -> Result<SymbolPath, RenameError> {
394 let old_path = self
396 .id_to_path
397 .get(id)
398 .ok_or(RenameError::InvalidId(id))?
399 .clone();
400
401 if self.path_to_id.contains_key(&new_path) {
403 return Err(RenameError::PathExists(Box::new(new_path)));
404 }
405
406 self.path_to_id.remove(&old_path);
408 self.path_to_id.insert(new_path.clone(), id);
409 self.id_to_path[id] = new_path;
410
411 Ok(old_path)
412 }
413
414 pub fn find_by_name(&self, name: &str) -> Vec<SymbolId> {
420 let mut results: Vec<SymbolId> = self
421 .id_to_path
422 .iter()
423 .filter(|(_, path)| path.name() == name)
424 .map(|(id, _)| id)
425 .collect();
426
427 for (alias_path, &canonical_id) in &self.alias_to_canonical {
429 if alias_path.name() == name && !results.contains(&canonical_id) {
430 results.push(canonical_id);
431 }
432 }
433
434 results
435 }
436
437 pub fn lookup_by_name(&self, name: &str) -> Option<SymbolId> {
439 self.id_to_path
440 .iter()
441 .find(|(_, path)| path.name() == name)
442 .map(|(id, _)| id)
443 }
444
445 pub fn register_reexport(
449 &mut self,
450 canonical_id: SymbolId,
451 alias_path: SymbolPath,
452 origin_file: WorkspaceFilePath,
453 ) -> Result<(), InvalidSymbolId> {
454 if !self.contains(canonical_id) {
455 return Err(InvalidSymbolId(canonical_id));
456 }
457
458 self.alias_to_canonical
460 .insert(alias_path.clone(), canonical_id);
461
462 let info = ReExportInfo {
464 alias_path,
465 origin_file,
466 };
467 self.re_exports
468 .entry(canonical_id)
469 .expect("canonical_id was validated by self.contains() above")
470 .or_default()
471 .push(info);
472
473 Ok(())
474 }
475
476 pub fn unregister_reexport(
478 &mut self,
479 alias_path: &SymbolPath,
480 ) -> Result<(), UnregisterReexportError> {
481 let canonical_id = self
482 .alias_to_canonical
483 .remove(alias_path)
484 .ok_or(UnregisterReexportError::NotFound)?;
485
486 if let Some(aliases) = self.re_exports.get_mut(canonical_id) {
487 aliases.retain(|info| &info.alias_path != alias_path);
488 if aliases.is_empty() {
489 self.re_exports.remove(canonical_id);
490 }
491 }
492
493 Ok(())
494 }
495
496 pub fn re_exports(&self, id: SymbolId) -> Option<&[ReExportInfo]> {
498 self.re_exports.get(id).map(|v| v.as_slice())
499 }
500
501 pub fn register_persistent(
526 &mut self,
527 path: SymbolPath,
528 kind: SymbolKind,
529 uuid: Option<Uuid>,
530 ) -> Result<(SymbolId, Uuid), RegistrationError> {
531 let id = self.register(path, kind)?;
533
534 if let Some(&existing_uuid) = self.id_to_uuid.get(id) {
536 if let Some(provided) = uuid {
538 if provided != existing_uuid {
539 return Err(RegistrationError::UuidConflict {
542 id,
543 existing: existing_uuid,
544 provided,
545 });
546 }
547 }
548 return Ok((id, existing_uuid));
549 }
550
551 let final_uuid = uuid.unwrap_or_else(Uuid::new_v4);
553 self.id_to_uuid.insert(id, final_uuid);
554 self.uuid_to_id.insert(final_uuid, id);
555
556 Ok((id, final_uuid))
557 }
558
559 pub fn assign_uuid(
567 &mut self,
568 id: SymbolId,
569 uuid: Option<Uuid>,
570 ) -> Result<Uuid, InvalidSymbolId> {
571 if !self.contains(id) {
572 return Err(InvalidSymbolId(id));
573 }
574
575 if let Some(&existing) = self.id_to_uuid.get(id) {
577 return Ok(existing);
578 }
579
580 let final_uuid = uuid.unwrap_or_else(Uuid::new_v4);
581 self.id_to_uuid.insert(id, final_uuid);
582 self.uuid_to_id.insert(final_uuid, id);
583
584 Ok(final_uuid)
585 }
586
587 #[inline]
591 pub fn uuid(&self, id: SymbolId) -> Option<Uuid> {
592 self.id_to_uuid.get(id).copied()
593 }
594
595 #[inline]
599 pub fn lookup_by_uuid(&self, uuid: Uuid) -> Option<SymbolId> {
600 self.uuid_to_id.get(&uuid).copied()
601 }
602
603 #[inline]
605 pub fn has_uuid(&self, id: SymbolId) -> bool {
606 self.id_to_uuid.contains_key(id)
607 }
608
609 pub fn iter_persistent(&self) -> impl Iterator<Item = (SymbolId, Uuid)> + '_ {
611 self.id_to_uuid.iter().map(|(id, &uuid)| (id, uuid))
612 }
613
614 pub fn persistent_count(&self) -> usize {
616 self.id_to_uuid.len()
617 }
618
619 pub fn preload_uuid_mapping(&mut self, mappings: HashMap<SymbolPath, Uuid>) {
638 self.preloaded_uuids = mappings;
639 }
640
641 pub fn export_uuid_mapping(&self) -> HashMap<SymbolPath, Uuid> {
653 self.id_to_path
654 .iter()
655 .filter_map(|(id, path)| self.id_to_uuid.get(id).map(|&uuid| (path.clone(), uuid)))
656 .collect()
657 }
658
659 pub fn export_uuid_mapping_strings(&self) -> HashMap<String, String> {
663 self.export_uuid_mapping()
664 .into_iter()
665 .map(|(path, uuid)| (path.to_string(), uuid.to_string()))
666 .collect()
667 }
668
669 pub fn preload_uuid_mapping_strings(&mut self, mappings: HashMap<String, String>) {
674 let parsed: HashMap<SymbolPath, Uuid> = mappings
675 .into_iter()
676 .filter_map(|(path_str, uuid_str)| {
677 let path = SymbolPath::parse(&path_str).ok()?;
678 let uuid = Uuid::parse_str(&uuid_str).ok()?;
679 Some((path, uuid))
680 })
681 .collect();
682 self.preloaded_uuids = parsed;
683 }
684
685 pub fn iter(&self) -> impl Iterator<Item = (SymbolId, &SymbolPath)> {
689 self.id_to_path.iter()
690 }
691
692 pub fn iter_by_kind(&self, kind: SymbolKind) -> impl Iterator<Item = SymbolId> + '_ {
694 self.kinds
695 .iter()
696 .filter(move |(_, &k)| k == kind)
697 .map(|(id, _)| id)
698 }
699
700 pub fn iter_in_crate<'a>(&'a self, crate_name: &'a str) -> impl Iterator<Item = SymbolId> + 'a {
702 self.id_to_path
703 .iter()
704 .filter(move |(_, path)| path.crate_name() == crate_name)
705 .map(|(id, _)| id)
706 }
707
708 pub fn iter_alias_name_id_pairs(&self) -> impl Iterator<Item = (&str, SymbolId)> {
720 self.alias_to_canonical
721 .iter()
722 .map(|(alias_path, &canonical_id)| (alias_path.name(), canonical_id))
723 }
724
725 pub fn len(&self) -> usize {
729 self.id_to_path.len()
730 }
731
732 pub fn is_empty(&self) -> bool {
734 self.id_to_path.is_empty()
735 }
736
737 pub fn memory_stats(&self) -> MemoryStats {
739 MemoryStats {
740 symbol_count: self.id_to_path.len(),
741 estimated_bytes: self.id_to_path.len() * 64 + self.path_to_id.len() * 80
744 + self.kinds.len() * 8
745 + self.spans.len() * 48
746 + self.visibility.len() * 16,
747 }
748 }
749}
750
751impl Default for SymbolRegistry {
752 fn default() -> Self {
753 Self::new()
754 }
755}
756
757#[derive(Debug, Clone)]
759pub struct MemoryStats {
760 pub symbol_count: usize,
762 pub estimated_bytes: usize,
765}
766
767#[cfg(test)]
768mod tests {
769 use super::*;
770
771 fn make_path(s: &str) -> SymbolPath {
772 SymbolPath::parse(s).unwrap()
773 }
774
775 #[test]
776 fn test_register_and_lookup() {
777 let mut registry = SymbolRegistry::new();
778
779 let path = make_path("my_crate::MyStruct");
780 let id = registry.register(path.clone(), SymbolKind::Struct).unwrap();
781
782 assert!(registry.contains(id));
783 assert_eq!(registry.lookup(&path), Some(id));
784 assert_eq!(registry.resolve(id), Some(&path));
785 assert_eq!(registry.kind(id), Some(SymbolKind::Struct));
786 }
787
788 #[test]
789 fn test_register_duplicate() {
790 let mut registry = SymbolRegistry::new();
791
792 let path = make_path("my_crate::MyStruct");
793 let id1 = registry.register(path.clone(), SymbolKind::Struct).unwrap();
794 let id2 = registry.register(path.clone(), SymbolKind::Struct).unwrap();
795
796 assert_eq!(id1, id2);
798 }
799
800 #[test]
801 fn test_register_conflicting_kind() {
802 let mut registry = SymbolRegistry::new();
803
804 let path = make_path("my_crate::MyStruct");
805 registry.register(path.clone(), SymbolKind::Struct).unwrap();
806
807 let result = registry.register(path, SymbolKind::Enum);
809 assert!(matches!(
810 result,
811 Err(RegistrationError::ConflictingKind { .. })
812 ));
813 }
814
815 #[test]
816 fn test_register_var() {
817 let mut registry = SymbolRegistry::new();
818
819 let fn_path = make_path("my_crate::my_fn");
821 let fn_id = registry.register(fn_path, SymbolKind::Function).unwrap();
822
823 let var_id = registry
825 .register_var(fn_id, VarScope::Local, "result", SymbolKind::Variable)
826 .unwrap();
827
828 let var_path = registry.resolve(var_id).unwrap();
830 assert_eq!(var_path.to_string(), "my_crate::my_fn::$var::result");
831 assert_eq!(registry.parent(var_id), Some(fn_id));
832 }
833
834 #[test]
835 fn test_reexport() {
836 let mut registry = SymbolRegistry::new();
837
838 let canonical_path = make_path("std::collections::hash_map::HashMap");
840 let canonical_id = registry
841 .register(canonical_path, SymbolKind::Struct)
842 .unwrap();
843
844 let alias_path = make_path("std::collections::HashMap");
846 let origin = WorkspaceFilePath::new_for_test("src/collections/mod.rs", "/std", "std");
847
848 registry
849 .register_reexport(canonical_id, alias_path.clone(), origin)
850 .unwrap();
851
852 assert_eq!(registry.lookup(&alias_path), Some(canonical_id));
854 }
855
856 #[test]
857 fn test_iter_by_kind() {
858 let mut registry = SymbolRegistry::new();
859
860 registry
861 .register(make_path("my_crate::Struct1"), SymbolKind::Struct)
862 .unwrap();
863 registry
864 .register(make_path("my_crate::Struct2"), SymbolKind::Struct)
865 .unwrap();
866 registry
867 .register(make_path("my_crate::func"), SymbolKind::Function)
868 .unwrap();
869
870 let structs: Vec<_> = registry.iter_by_kind(SymbolKind::Struct).collect();
871 assert_eq!(structs.len(), 2);
872
873 let funcs: Vec<_> = registry.iter_by_kind(SymbolKind::Function).collect();
874 assert_eq!(funcs.len(), 1);
875 }
876
877 #[test]
880 fn test_register_persistent_new() {
881 let mut registry = SymbolRegistry::new();
882
883 let path = make_path("my_crate::MyStruct");
884 let (id, uuid) = registry
885 .register_persistent(path.clone(), SymbolKind::Struct, None)
886 .unwrap();
887
888 assert_eq!(registry.uuid(id), Some(uuid));
890 assert_eq!(registry.lookup_by_uuid(uuid), Some(id));
891 assert!(registry.has_uuid(id));
892 }
893
894 #[test]
895 fn test_register_persistent_returns_auto_uuid() {
896 let mut registry = SymbolRegistry::new();
897
898 let path = make_path("my_crate::MyStruct");
901 let (id, uuid) = registry
902 .register_persistent(path, SymbolKind::Struct, None)
903 .unwrap();
904
905 assert_eq!(registry.uuid(id), Some(uuid));
907 assert_eq!(registry.lookup_by_uuid(uuid), Some(id));
908 }
909
910 #[test]
911 fn test_register_persistent_idempotent() {
912 let mut registry = SymbolRegistry::new();
913
914 let path = make_path("my_crate::MyStruct");
915
916 let (id1, uuid1) = registry
918 .register_persistent(path.clone(), SymbolKind::Struct, None)
919 .unwrap();
920
921 let (id2, uuid2) = registry
923 .register_persistent(path, SymbolKind::Struct, None)
924 .unwrap();
925
926 assert_eq!(id1, id2);
928 assert_eq!(uuid1, uuid2);
929 }
930
931 #[test]
932 fn test_uuid_survives_rename() {
933 let mut registry = SymbolRegistry::new();
934
935 let old_path = make_path("my_crate::OldName");
937 let (id, uuid) = registry
938 .register_persistent(old_path, SymbolKind::Struct, None)
939 .unwrap();
940
941 let new_path = make_path("my_crate::NewName");
943 registry.rename(id, new_path.clone()).unwrap();
944
945 assert_eq!(registry.uuid(id), Some(uuid));
947 assert_eq!(registry.lookup_by_uuid(uuid), Some(id));
948
949 assert_eq!(registry.resolve(id), Some(&new_path));
951 }
952
953 #[test]
954 fn test_uuid_removed_on_delete() {
955 let mut registry = SymbolRegistry::new();
956
957 let path = make_path("my_crate::MyStruct");
958 let (id, uuid) = registry
959 .register_persistent(path, SymbolKind::Struct, None)
960 .unwrap();
961
962 registry.remove(id);
964
965 assert!(registry.uuid(id).is_none());
967 assert!(registry.lookup_by_uuid(uuid).is_none());
968 }
969
970 #[test]
971 fn test_auto_uuid_on_register() {
972 let mut registry = SymbolRegistry::new();
973
974 let path = make_path("my_crate::MyStruct");
976 let id = registry.register(path, SymbolKind::Struct).unwrap();
977
978 assert!(registry.has_uuid(id));
980 let uuid = registry.uuid(id).unwrap();
981 assert_eq!(registry.lookup_by_uuid(uuid), Some(id));
982
983 let uuid2 = registry.assign_uuid(id, None).unwrap();
985 assert_eq!(uuid, uuid2);
986 }
987
988 #[test]
989 fn test_iter_persistent() {
990 let mut registry = SymbolRegistry::new();
991
992 let id0 = registry
994 .register(make_path("my_crate::Symbol0"), SymbolKind::Struct)
995 .unwrap();
996 let id1 = registry
997 .register(make_path("my_crate::Symbol1"), SymbolKind::Struct)
998 .unwrap();
999 let id2 = registry
1000 .register(make_path("my_crate::Symbol2"), SymbolKind::Enum)
1001 .unwrap();
1002
1003 let persistent: Vec<_> = registry.iter_persistent().collect();
1005 assert_eq!(persistent.len(), 3);
1006 assert_eq!(registry.persistent_count(), 3);
1007
1008 assert!(registry.has_uuid(id0));
1010 assert!(registry.has_uuid(id1));
1011 assert!(registry.has_uuid(id2));
1012 }
1013
1014 #[test]
1015 fn test_uuid_conflict_error() {
1016 let mut registry = SymbolRegistry::new();
1017
1018 let path = make_path("my_crate::MyStruct");
1019 let different_uuid = Uuid::new_v4();
1020
1021 let id = registry.register(path.clone(), SymbolKind::Struct).unwrap();
1023 let auto_uuid = registry.uuid(id).unwrap();
1024
1025 let result = registry.register_persistent(path, SymbolKind::Struct, Some(different_uuid));
1027
1028 assert!(matches!(
1029 result,
1030 Err(RegistrationError::UuidConflict { .. })
1031 ));
1032
1033 assert_eq!(registry.uuid(id), Some(auto_uuid));
1035 }
1036
1037 #[test]
1040 fn test_find_by_name_includes_aliases() {
1041 let mut registry = SymbolRegistry::new();
1042
1043 let canonical_path = make_path("parking_lot::Mutex");
1045 let canonical_id = registry
1046 .register(canonical_path, SymbolKind::Struct)
1047 .unwrap();
1048
1049 let alias_path = make_path("tokio::sync::Mutex");
1051 let file_path = WorkspaceFilePath::new_for_test("src/sync/mod.rs", "/tmp/tokio", "tokio");
1052 registry
1053 .register_reexport(canonical_id, alias_path, file_path)
1054 .unwrap();
1055
1056 let results = registry.find_by_name("Mutex");
1058 assert_eq!(results.len(), 1);
1059 assert_eq!(results[0], canonical_id);
1060 }
1061
1062 #[test]
1063 fn test_find_by_name_no_duplicate_with_alias() {
1064 let mut registry = SymbolRegistry::new();
1065
1066 let canonical_path = make_path("parking_lot::Mutex");
1068 let canonical_id = registry
1069 .register(canonical_path, SymbolKind::Struct)
1070 .unwrap();
1071
1072 let alias_path = make_path("tokio::sync::Mutex");
1074 let file_path = WorkspaceFilePath::new_for_test("src/sync/mod.rs", "/tmp/tokio", "tokio");
1075 registry
1076 .register_reexport(canonical_id, alias_path, file_path)
1077 .unwrap();
1078
1079 let results = registry.find_by_name("Mutex");
1081 assert_eq!(results.len(), 1);
1082 }
1083
1084 #[test]
1085 fn test_lookup_resolves_alias() {
1086 let mut registry = SymbolRegistry::new();
1087
1088 let canonical_path = make_path("my_crate::inner::Config");
1089 let canonical_id = registry
1090 .register(canonical_path, SymbolKind::Struct)
1091 .unwrap();
1092
1093 let alias_path = make_path("my_crate::Config");
1094 let file_path = WorkspaceFilePath::new_for_test("src/lib.rs", "/tmp/my_crate", "my_crate");
1095 registry
1096 .register_reexport(canonical_id, alias_path.clone(), file_path)
1097 .unwrap();
1098
1099 assert_eq!(registry.lookup(&alias_path), Some(canonical_id));
1101 }
1102}