1use anyhow::Result;
11use std::collections::HashMap;
12
13#[repr(C)]
16#[derive(Clone, Copy, Debug, PartialEq)]
17pub struct SymbolMetadataRec {
18 pub symbol_id: u64,
20
21 pub name_offset: u32,
23 pub fqn_offset: u32,
25 pub file_path_offset: u32,
27
28 pub kind: u8,
30 pub language: u8,
32
33 pub _padding1: u16,
35 pub _padding2: u32,
36
37 pub byte_start: u64,
39 pub byte_end: u64,
40
41 pub start_line: u64,
43 pub start_col: u64,
44 pub end_line: u64,
45 pub end_col: u64,
46}
47
48impl SymbolMetadataRec {
49 pub const SIZE: usize = 80; }
51
52unsafe impl bytemuck::Pod for SymbolMetadataRec {}
54unsafe impl bytemuck::Zeroable for SymbolMetadataRec {}
55
56#[derive(Debug, Clone, PartialEq)]
58pub struct SymbolMetadata {
59 pub symbol_id: u64,
60 pub name: String,
61 pub fqn: String,
62 pub file_path: String,
63 pub kind: u8,
64 pub language: u8,
65 pub byte_start: u64,
66 pub byte_end: u64,
67 pub start_line: u64,
68 pub start_col: u64,
69 pub end_line: u64,
70 pub end_col: u64,
71}
72
73#[derive(Debug, Clone, Default)]
75pub struct StringTable {
76 data: Vec<u8>,
78 offset_map: HashMap<String, u32>,
80}
81
82impl StringTable {
83 pub fn new() -> Self {
84 Self {
85 data: Vec::new(),
86 offset_map: HashMap::new(),
87 }
88 }
89
90 pub fn add(&mut self, s: &str) -> u32 {
93 if let Some(&offset) = self.offset_map.get(s) {
94 return offset;
95 }
96
97 let offset = self.data.len() as u32;
98 self.data.extend_from_slice(s.as_bytes());
99 self.data.push(0); self.offset_map.insert(s.to_string(), offset);
102 offset
103 }
104
105 pub fn get(&self, offset: u32) -> Option<String> {
107 if offset as usize >= self.data.len() {
108 return None;
109 }
110
111 let start = offset as usize;
112 let end = self.data[start..].iter().position(|&b| b == 0)?;
113
114 String::from_utf8(self.data[start..start + end].to_vec()).ok()
115 }
116
117 pub fn to_bytes(&self) -> Vec<u8> {
119 let mut bytes = Vec::with_capacity(8 + self.data.len());
121 bytes.extend_from_slice(&(self.data.len() as u64).to_le_bytes());
122 bytes.extend_from_slice(&self.data);
123 bytes
124 }
125
126 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
128 if bytes.len() < 8 {
129 anyhow::bail!("String table too short");
130 }
131
132 let data_len = u64::from_le_bytes(bytes[0..8].try_into()?) as usize;
133 if bytes.len() < 8 + data_len {
134 anyhow::bail!("String table data truncated");
135 }
136
137 let data = bytes[8..8 + data_len].to_vec();
138
139 let mut offset_map = HashMap::new();
141 let mut offset = 0;
142 while offset < data.len() {
143 let end = data[offset..]
144 .iter()
145 .position(|&b| b == 0)
146 .map(|p| offset + p)
147 .unwrap_or(data.len());
148
149 if let Ok(s) = String::from_utf8(data[offset..end].to_vec()) {
150 offset_map.insert(s, offset as u32);
151 }
152
153 offset = end + 1; }
155
156 Ok(Self { data, offset_map })
157 }
158
159 pub fn is_empty(&self) -> bool {
160 self.data.is_empty()
161 }
162
163 pub fn len(&self) -> usize {
164 self.offset_map.len()
165 }
166}
167
168#[derive(Debug, Clone, Default)]
170pub struct FileInfo {
171 pub path: String,
172 pub hash: Option<String>, pub last_indexed_at: i64, }
175
176#[derive(Debug, Clone, Default)]
178pub struct FileTable {
179 path_to_id: HashMap<String, u32>,
181 id_to_info: HashMap<u32, FileInfo>,
183 next_id: u32,
185}
186
187impl FileTable {
188 pub fn new() -> Self {
189 Self {
190 path_to_id: HashMap::new(),
191 id_to_info: HashMap::new(),
192 next_id: 1, }
194 }
195
196 pub fn get_or_assign_id(&mut self, path: &str) -> u32 {
198 if let Some(&id) = self.path_to_id.get(path) {
199 return id;
200 }
201
202 let id = self.next_id;
203 self.next_id += 1;
204
205 self.path_to_id.insert(path.to_string(), id);
206 self.id_to_info.insert(
207 id,
208 FileInfo {
209 path: path.to_string(),
210 hash: None,
211 last_indexed_at: 0,
212 },
213 );
214
215 id
216 }
217
218 pub fn get_path(&self, id: u32) -> Option<&str> {
220 self.id_to_info.get(&id).map(|info| info.path.as_str())
221 }
222
223 pub fn get_info(&self, id: u32) -> Option<&FileInfo> {
225 self.id_to_info.get(&id)
226 }
227
228 pub fn get_info_by_path(&self, path: &str) -> Option<&FileInfo> {
230 self.path_to_id
231 .get(path)
232 .and_then(|&id| self.id_to_info.get(&id))
233 }
234
235 pub fn set_file_hash(&mut self, path: &str, hash: &str) {
237 let id = self.get_or_assign_id(path);
238 if let Some(info) = self.id_to_info.get_mut(&id) {
239 info.hash = Some(hash.to_string());
240 info.last_indexed_at = std::time::SystemTime::now()
241 .duration_since(std::time::UNIX_EPOCH)
242 .unwrap_or_default()
243 .as_secs() as i64;
244 }
245 }
246
247 pub fn get_file_hash(&self, path: &str) -> Option<&str> {
249 self.get_info_by_path(path)
250 .and_then(|info| info.hash.as_deref())
251 }
252
253 pub fn get_id(&self, path: &str) -> Option<u32> {
255 self.path_to_id.get(path).copied()
256 }
257
258 pub fn file_count(&self) -> usize {
260 self.path_to_id.len()
261 }
262
263 pub fn all_paths(&self) -> Vec<&str> {
265 self.id_to_info
266 .values()
267 .map(|info| info.path.as_str())
268 .collect()
269 }
270
271 pub fn all_files(&self) -> Vec<&FileInfo> {
273 self.id_to_info.values().collect()
274 }
275
276 pub fn to_bytes(&self) -> Vec<u8> {
278 let mut bytes = Vec::new();
281 bytes.extend_from_slice(&(self.path_to_id.len() as u64).to_le_bytes());
282
283 for (id, info) in &self.id_to_info {
284 bytes.extend_from_slice(&id.to_le_bytes());
285 bytes.extend_from_slice(&(info.path.len() as u32).to_le_bytes());
286 let hash_len = info.hash.as_ref().map(|h| h.len()).unwrap_or(0) as u32;
287 bytes.extend_from_slice(&hash_len.to_le_bytes());
288 bytes.extend_from_slice(&info.last_indexed_at.to_le_bytes());
289 bytes.extend_from_slice(info.path.as_bytes());
290 if let Some(hash) = &info.hash {
291 bytes.extend_from_slice(hash.as_bytes());
292 }
293 }
294
295 bytes
296 }
297
298 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
300 if bytes.len() < 8 {
301 anyhow::bail!("File table too short");
302 }
303
304 let count = u64::from_le_bytes(bytes[0..8].try_into()?) as usize;
305 let mut offset = 8;
306
307 let mut path_to_id = HashMap::new();
308 let mut id_to_info = HashMap::new();
309 let mut max_id = 0;
310
311 for _ in 0..count {
312 if offset + 20 > bytes.len() {
313 anyhow::bail!("File table entry truncated");
314 }
315
316 let id = u32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
317 let path_len = u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into()?) as usize;
318 let hash_len = u32::from_le_bytes(bytes[offset + 8..offset + 12].try_into()?) as usize;
319 let last_indexed_at = i64::from_le_bytes(bytes[offset + 12..offset + 20].try_into()?);
320 offset += 20;
321
322 if offset + path_len + hash_len > bytes.len() {
323 anyhow::bail!("File data truncated");
324 }
325
326 let path = String::from_utf8(bytes[offset..offset + path_len].to_vec())?;
327 offset += path_len;
328
329 let hash = if hash_len > 0 {
330 Some(String::from_utf8(
331 bytes[offset..offset + hash_len].to_vec(),
332 )?)
333 } else {
334 None
335 };
336 offset += hash_len;
337
338 path_to_id.insert(path.clone(), id);
339 id_to_info.insert(
340 id,
341 FileInfo {
342 path,
343 hash,
344 last_indexed_at,
345 },
346 );
347 max_id = max_id.max(id);
348 }
349
350 Ok(Self {
351 path_to_id,
352 id_to_info,
353 next_id: max_id + 1,
354 })
355 }
356}
357
358#[derive(Debug, Clone, Default)]
360pub struct SymbolMetadataStore {
361 pub metadata: HashMap<u64, SymbolMetadataRec>,
363 pub strings: StringTable,
365 pub files: FileTable,
367}
368
369impl SymbolMetadataStore {
370 pub fn new() -> Self {
371 Self {
372 metadata: HashMap::new(),
373 strings: StringTable::new(),
374 files: FileTable::new(),
375 }
376 }
377
378 pub fn add(&mut self, meta: SymbolMetadata) {
380 let name_offset = self.strings.add(&meta.name);
381 let fqn_offset = self.strings.add(&meta.fqn);
382 let file_path_offset = self.strings.add(&meta.file_path);
383
384 self.files.get_or_assign_id(&meta.file_path);
386
387 let rec = SymbolMetadataRec {
388 symbol_id: meta.symbol_id,
389 name_offset,
390 fqn_offset,
391 file_path_offset,
392 kind: meta.kind,
393 language: meta.language,
394 _padding1: 0,
395 _padding2: 0,
396 byte_start: meta.byte_start,
397 byte_end: meta.byte_end,
398 start_line: meta.start_line,
399 start_col: meta.start_col,
400 end_line: meta.end_line,
401 end_col: meta.end_col,
402 };
403
404 self.metadata.insert(meta.symbol_id, rec);
405 }
406
407 pub fn get(&self, symbol_id: u64) -> Option<SymbolMetadata> {
409 let rec = self.metadata.get(&symbol_id)?;
410
411 Some(SymbolMetadata {
412 symbol_id: rec.symbol_id,
413 name: self.strings.get(rec.name_offset)?,
414 fqn: self.strings.get(rec.fqn_offset)?,
415 file_path: self.strings.get(rec.file_path_offset)?,
416 kind: rec.kind,
417 language: rec.language,
418 byte_start: rec.byte_start,
419 byte_end: rec.byte_end,
420 start_line: rec.start_line,
421 start_col: rec.start_col,
422 end_line: rec.end_line,
423 end_col: rec.end_col,
424 })
425 }
426
427 pub fn find_by_fqn(&self, fqn: &str) -> Option<u64> {
429 let target_offset = self.strings.offset_map.get(fqn)?;
430
431 self.metadata
432 .values()
433 .find(|rec| rec.fqn_offset == *target_offset)
434 .map(|rec| rec.symbol_id)
435 }
436
437 pub fn find_by_name(&self, name: &str) -> Vec<u64> {
439 let Some(&target_offset) = self.strings.offset_map.get(name) else {
440 return Vec::new();
441 };
442
443 self.metadata
444 .values()
445 .filter(|rec| rec.name_offset == target_offset)
446 .map(|rec| rec.symbol_id)
447 .collect()
448 }
449
450 pub fn symbols_in_file(&self, file_path: &str) -> Vec<u64> {
452 let Some(&target_offset) = self.strings.offset_map.get(file_path) else {
453 return Vec::new();
454 };
455
456 self.metadata
457 .values()
458 .filter(|rec| rec.file_path_offset == target_offset)
459 .map(|rec| rec.symbol_id)
460 .collect()
461 }
462
463 pub fn symbol_count(&self) -> usize {
465 self.metadata.len()
466 }
467
468 pub fn file_count(&self) -> usize {
470 self.files.file_count()
471 }
472
473 pub fn all_file_paths(&self) -> Vec<String> {
475 self.files
476 .all_paths()
477 .into_iter()
478 .map(|s| s.to_string())
479 .collect()
480 }
481
482 pub fn all_symbol_ids(&self) -> Vec<u64> {
484 self.metadata.keys().copied().collect()
485 }
486
487 pub fn to_bytes(&self) -> Vec<u8> {
489 let mut bytes = Vec::new();
496
497 bytes.extend_from_slice(&(self.metadata.len() as u64).to_le_bytes());
499
500 for rec in self.metadata.values() {
502 bytes.extend_from_slice(bytemuck::bytes_of(rec));
503 }
504
505 let string_bytes = self.strings.to_bytes();
507 bytes.extend_from_slice(&(string_bytes.len() as u64).to_le_bytes());
508 bytes.extend_from_slice(&string_bytes);
509
510 let file_bytes = self.files.to_bytes();
512 bytes.extend_from_slice(&(file_bytes.len() as u64).to_le_bytes());
513 bytes.extend_from_slice(&file_bytes);
514
515 bytes
516 }
517
518 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
520 let mut offset = 0;
521
522 if bytes.len() < 8 {
524 anyhow::bail!("Symbol metadata too short for count");
525 }
526 let metadata_count = u64::from_le_bytes(bytes[offset..offset + 8].try_into()?) as usize;
527 offset += 8;
528
529 let mut metadata = HashMap::with_capacity(metadata_count);
531 let rec_size = std::mem::size_of::<SymbolMetadataRec>();
532
533 for _ in 0..metadata_count {
534 if offset + rec_size > bytes.len() {
535 anyhow::bail!("Metadata record truncated");
536 }
537 let rec_bytes = &bytes[offset..offset + rec_size];
538 let rec: SymbolMetadataRec = match bytemuck::try_from_bytes(rec_bytes) {
539 Ok(r) => *r,
540 Err(e) => anyhow::bail!("Failed to parse metadata record: {:?}", e),
541 };
542 offset += rec_size;
543 metadata.insert(rec.symbol_id, rec);
544 }
545
546 if offset + 8 > bytes.len() {
548 anyhow::bail!("Missing string table length");
549 }
550 let string_table_len = u64::from_le_bytes(bytes[offset..offset + 8].try_into()?) as usize;
551 offset += 8;
552
553 if offset + string_table_len > bytes.len() {
554 anyhow::bail!("String table truncated");
555 }
556 let strings = StringTable::from_bytes(&bytes[offset..offset + string_table_len])?;
557 offset += string_table_len;
558
559 if offset + 8 > bytes.len() {
561 anyhow::bail!("Missing file table length");
562 }
563 let file_table_len = u64::from_le_bytes(bytes[offset..offset + 8].try_into()?) as usize;
564 offset += 8;
565
566 if offset + file_table_len > bytes.len() {
567 anyhow::bail!("File table truncated");
568 }
569 let files = FileTable::from_bytes(&bytes[offset..offset + file_table_len])?;
570
571 Ok(Self {
572 metadata,
573 strings,
574 files,
575 })
576 }
577
578 pub fn set_file_hash(&mut self, path: &str, hash: &str) {
580 self.files.set_file_hash(path, hash);
581 }
582
583 pub fn get_file_hash(&self, path: &str) -> Option<&str> {
585 self.files.get_file_hash(path)
586 }
587
588 pub fn all_files(&self) -> Vec<&FileInfo> {
590 self.files.all_files()
591 }
592
593 pub fn get_file_info(&self, path: &str) -> Option<&FileInfo> {
595 self.files.get_info_by_path(path)
596 }
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602
603 #[test]
604 fn test_string_table_basic() {
605 let mut table = StringTable::new();
606
607 let offset1 = table.add("hello");
608 let offset2 = table.add("world");
609 let offset3 = table.add("hello"); assert_eq!(offset1, offset3); assert_ne!(offset1, offset2);
613
614 assert_eq!(table.get(offset1), Some("hello".to_string()));
615 assert_eq!(table.get(offset2), Some("world".to_string()));
616 }
617
618 #[test]
619 fn test_string_table_serialization() {
620 let mut table = StringTable::new();
621 table.add("foo");
622 table.add("bar");
623
624 let bytes = table.to_bytes();
625 let restored = StringTable::from_bytes(&bytes).unwrap();
626
627 assert_eq!(restored.len(), 2);
628 assert!(restored.get(0).is_some());
629 }
630
631 #[test]
632 fn test_file_table_basic() {
633 let mut table = FileTable::new();
634
635 let id1 = table.get_or_assign_id("/src/main.rs");
636 let id2 = table.get_or_assign_id("/src/lib.rs");
637 let id3 = table.get_or_assign_id("/src/main.rs"); assert_eq!(id1, id3);
640 assert_ne!(id1, id2);
641
642 assert_eq!(table.file_count(), 2);
643 assert_eq!(table.get_path(id1), Some("/src/main.rs"));
644 assert_eq!(table.get_id("/src/lib.rs"), Some(id2));
645 }
646
647 #[test]
648 fn test_file_table_serialization() {
649 let mut table = FileTable::new();
650 table.get_or_assign_id("/a.rs");
651 table.get_or_assign_id("/b.rs");
652
653 let bytes = table.to_bytes();
654 let restored = FileTable::from_bytes(&bytes).unwrap();
655
656 assert_eq!(restored.file_count(), 2);
657 assert!(restored.get_id("/a.rs").is_some());
658 assert!(restored.get_id("/b.rs").is_some());
659 }
660
661 #[test]
662 fn test_symbol_metadata_store_basic() {
663 let mut store = SymbolMetadataStore::new();
664
665 let meta = SymbolMetadata {
666 symbol_id: 1,
667 name: "my_func".to_string(),
668 fqn: "crate::my_func".to_string(),
669 file_path: "/src/lib.rs".to_string(),
670 kind: 1,
671 language: 1,
672 byte_start: 100,
673 byte_end: 200,
674 start_line: 10,
675 start_col: 0,
676 end_line: 20,
677 end_col: 1,
678 };
679
680 store.add(meta.clone());
681
682 assert_eq!(store.symbol_count(), 1);
683 assert_eq!(store.file_count(), 1);
684
685 let retrieved = store.get(1).unwrap();
686 assert_eq!(retrieved.name, "my_func");
687 assert_eq!(retrieved.fqn, "crate::my_func");
688 assert_eq!(retrieved.file_path, "/src/lib.rs");
689 }
690
691 #[test]
692 fn test_symbol_metadata_find_by_fqn() {
693 let mut store = SymbolMetadataStore::new();
694
695 store.add(SymbolMetadata {
696 symbol_id: 1,
697 name: "func1".to_string(),
698 fqn: "crate::module::func1".to_string(),
699 file_path: "/src/lib.rs".to_string(),
700 kind: 1,
701 language: 1,
702 byte_start: 0,
703 byte_end: 10,
704 start_line: 0,
705 start_col: 0,
706 end_line: 0,
707 end_col: 0,
708 });
709
710 store.add(SymbolMetadata {
711 symbol_id: 2,
712 name: "func2".to_string(),
713 fqn: "crate::module::func2".to_string(),
714 file_path: "/src/lib.rs".to_string(),
715 kind: 1,
716 language: 1,
717 byte_start: 20,
718 byte_end: 30,
719 start_line: 0,
720 start_col: 0,
721 end_line: 0,
722 end_col: 0,
723 });
724
725 assert_eq!(store.find_by_fqn("crate::module::func1"), Some(1));
726 assert_eq!(store.find_by_fqn("crate::module::func2"), Some(2));
727 assert_eq!(store.find_by_fqn("nonexistent"), None);
728 }
729
730 #[test]
731 fn test_symbol_metadata_find_by_name() {
732 let mut store = SymbolMetadataStore::new();
733
734 store.add(SymbolMetadata {
735 symbol_id: 1,
736 name: "foo".to_string(),
737 fqn: "crate::A::foo".to_string(),
738 file_path: "/src/a.rs".to_string(),
739 kind: 1,
740 language: 1,
741 byte_start: 0,
742 byte_end: 10,
743 start_line: 0,
744 start_col: 0,
745 end_line: 0,
746 end_col: 0,
747 });
748
749 store.add(SymbolMetadata {
750 symbol_id: 2,
751 name: "foo".to_string(), fqn: "crate::B::foo".to_string(),
753 file_path: "/src/b.rs".to_string(),
754 kind: 1,
755 language: 1,
756 byte_start: 0,
757 byte_end: 10,
758 start_line: 0,
759 start_col: 0,
760 end_line: 0,
761 end_col: 0,
762 });
763
764 let results = store.find_by_name("foo");
765 assert_eq!(results.len(), 2);
766 assert!(results.contains(&1));
767 assert!(results.contains(&2));
768 }
769
770 #[test]
771 fn test_symbol_metadata_symbols_in_file() {
772 let mut store = SymbolMetadataStore::new();
773
774 store.add(SymbolMetadata {
775 symbol_id: 1,
776 name: "func1".to_string(),
777 fqn: "crate::func1".to_string(),
778 file_path: "/src/main.rs".to_string(),
779 kind: 1,
780 language: 1,
781 byte_start: 0,
782 byte_end: 10,
783 start_line: 0,
784 start_col: 0,
785 end_line: 0,
786 end_col: 0,
787 });
788
789 store.add(SymbolMetadata {
790 symbol_id: 2,
791 name: "func2".to_string(),
792 fqn: "crate::func2".to_string(),
793 file_path: "/src/lib.rs".to_string(),
794 kind: 1,
795 language: 1,
796 byte_start: 0,
797 byte_end: 10,
798 start_line: 0,
799 start_col: 0,
800 end_line: 0,
801 end_col: 0,
802 });
803
804 let main_symbols = store.symbols_in_file("/src/main.rs");
805 assert_eq!(main_symbols.len(), 1);
806 assert_eq!(main_symbols[0], 1);
807
808 assert_eq!(store.file_count(), 2);
809 }
810
811 #[test]
812 fn test_symbol_metadata_store_serialization() {
813 let mut store = SymbolMetadataStore::new();
814
815 store.add(SymbolMetadata {
816 symbol_id: 42,
817 name: "test_function".to_string(),
818 fqn: "my_crate::test_function".to_string(),
819 file_path: "/home/user/project/src/lib.rs".to_string(),
820 kind: 2,
821 language: 1,
822 byte_start: 150,
823 byte_end: 300,
824 start_line: 15,
825 start_col: 4,
826 end_line: 25,
827 end_col: 5,
828 });
829
830 let bytes = store.to_bytes();
831 let restored = SymbolMetadataStore::from_bytes(&bytes).unwrap();
832
833 assert_eq!(restored.symbol_count(), 1);
834 assert_eq!(restored.file_count(), 1);
835
836 let meta = restored.get(42).unwrap();
837 assert_eq!(meta.name, "test_function");
838 assert_eq!(meta.fqn, "my_crate::test_function");
839 assert_eq!(meta.file_path, "/home/user/project/src/lib.rs");
840 assert_eq!(meta.byte_start, 150);
841 assert_eq!(meta.byte_end, 300);
842 assert_eq!(meta.start_line, 15);
843 assert_eq!(meta.start_col, 4);
844 assert_eq!(meta.end_line, 25);
845 assert_eq!(meta.end_col, 5);
846 }
847
848 #[test]
849 fn test_symbol_metadata_store_reopen_preserves_all() {
850 let mut store = SymbolMetadataStore::new();
851
852 for i in 0..10 {
854 store.add(SymbolMetadata {
855 symbol_id: i as u64,
856 name: format!("func{}", i),
857 fqn: format!("crate::module::func{}", i),
858 file_path: format!("/src/file{}.rs", i % 3), kind: (i % 5) as u8,
860 language: 1,
861 byte_start: i as u64 * 100,
862 byte_end: i as u64 * 100 + 50,
863 start_line: i as u64,
864 start_col: 0,
865 end_line: i as u64 + 5,
866 end_col: 1,
867 });
868 }
869
870 let bytes = store.to_bytes();
872 let restored = SymbolMetadataStore::from_bytes(&bytes).unwrap();
873
874 assert_eq!(restored.symbol_count(), 10);
876 assert_eq!(restored.file_count(), 3);
877
878 for i in 0..10 {
880 let meta = restored.get(i as u64).unwrap();
881 assert_eq!(meta.name, format!("func{}", i));
882 assert_eq!(meta.fqn, format!("crate::module::func{}", i));
883
884 assert_eq!(restored.find_by_fqn(&meta.fqn), Some(i as u64));
886 }
887
888 let file0_symbols = restored.symbols_in_file("/src/file0.rs");
890 assert_eq!(file0_symbols.len(), 4); }
892}