greppy/trace/types.rs
1//! Core Data Structures for Semantic Index
2//!
3//! All structures use #[repr(C)] for mmap compatibility and predictable memory layout.
4//! This enables zero-copy loading via memory-mapped files.
5//!
6//! @module trace/types
7
8use bitflags::bitflags;
9use serde::{Deserialize, Serialize};
10
11// =============================================================================
12// SYMBOL KIND ENUM
13// =============================================================================
14
15/// Classification of symbol definitions
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[repr(u8)]
18pub enum SymbolKind {
19 /// Function definition (standalone)
20 Function = 0,
21 /// Method definition (attached to class/struct)
22 Method = 1,
23 /// Class definition
24 Class = 2,
25 /// Struct definition
26 Struct = 3,
27 /// Enum definition
28 Enum = 4,
29 /// Interface/Trait definition
30 Interface = 5,
31 /// Type alias
32 TypeAlias = 6,
33 /// Constant definition
34 Constant = 7,
35 /// Variable binding
36 Variable = 8,
37 /// Module/namespace
38 Module = 9,
39 /// Unknown symbol type
40 Unknown = 255,
41}
42
43impl From<u8> for SymbolKind {
44 fn from(value: u8) -> Self {
45 match value {
46 0 => Self::Function,
47 1 => Self::Method,
48 2 => Self::Class,
49 3 => Self::Struct,
50 4 => Self::Enum,
51 5 => Self::Interface,
52 6 => Self::TypeAlias,
53 7 => Self::Constant,
54 8 => Self::Variable,
55 9 => Self::Module,
56 _ => Self::Unknown,
57 }
58 }
59}
60
61// =============================================================================
62// SYMBOL FLAGS
63// =============================================================================
64
65bitflags! {
66 /// Flags for symbol metadata
67 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
68 #[repr(transparent)]
69 pub struct SymbolFlags: u8 {
70 /// Symbol is an entry point (main, test, exported handler)
71 const IS_ENTRY_POINT = 0b0000_0001;
72 /// Symbol is exported (pub, export)
73 const IS_EXPORTED = 0b0000_0010;
74 /// Symbol is async
75 const IS_ASYNC = 0b0000_0100;
76 /// Symbol is a test function
77 const IS_TEST = 0b0000_1000;
78 /// Symbol is deprecated
79 const IS_DEPRECATED = 0b0001_0000;
80 /// Symbol is static/class-level
81 const IS_STATIC = 0b0010_0000;
82 /// Symbol is abstract
83 const IS_ABSTRACT = 0b0100_0000;
84 /// Symbol is a constructor
85 const IS_CONSTRUCTOR = 0b1000_0000;
86 }
87}
88
89impl Default for SymbolFlags {
90 fn default() -> Self {
91 Self::empty()
92 }
93}
94
95// =============================================================================
96// SYMBOL
97// =============================================================================
98
99/// A code symbol definition (function, class, method, etc.)
100///
101/// Layout (24 bytes):
102/// - id: u32 (4)
103/// - name_offset: u32 (4)
104/// - file_id: u16 (2)
105/// - kind: u8 (1)
106/// - flags: u8 (1)
107/// - start_line: u32 (4)
108/// - end_line: u32 (4)
109/// - _padding: u32 (4) - for alignment
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111#[repr(C)]
112pub struct Symbol {
113 /// Unique symbol ID (index into symbols vector)
114 pub id: u32,
115 /// Offset into string table for the symbol name
116 pub name_offset: u32,
117 /// File ID where this symbol is defined
118 pub file_id: u16,
119 /// Symbol kind (function, class, method, etc.)
120 pub kind: u8,
121 /// Symbol flags (entry point, exported, async, etc.)
122 pub flags: u8,
123 /// Starting line number (1-indexed)
124 pub start_line: u32,
125 /// Ending line number (1-indexed)
126 pub end_line: u32,
127 /// Padding for alignment
128 _padding: u32,
129}
130
131impl Symbol {
132 /// Create a new symbol
133 #[inline]
134 pub const fn new(
135 id: u32,
136 name_offset: u32,
137 file_id: u16,
138 kind: SymbolKind,
139 flags: SymbolFlags,
140 start_line: u32,
141 end_line: u32,
142 ) -> Self {
143 Self {
144 id,
145 name_offset,
146 file_id,
147 kind: kind as u8,
148 flags: flags.bits(),
149 start_line,
150 end_line,
151 _padding: 0,
152 }
153 }
154
155 /// Get the symbol kind
156 #[inline]
157 pub fn symbol_kind(&self) -> SymbolKind {
158 SymbolKind::from(self.kind)
159 }
160
161 /// Get the symbol flags
162 #[inline]
163 pub fn symbol_flags(&self) -> SymbolFlags {
164 SymbolFlags::from_bits_truncate(self.flags)
165 }
166
167 /// Check if this symbol is an entry point
168 #[inline]
169 pub fn is_entry_point(&self) -> bool {
170 self.symbol_flags().contains(SymbolFlags::IS_ENTRY_POINT)
171 }
172
173 /// Check if this symbol is exported
174 #[inline]
175 pub fn is_exported(&self) -> bool {
176 self.symbol_flags().contains(SymbolFlags::IS_EXPORTED)
177 }
178
179 /// Check if this symbol is async
180 #[inline]
181 pub fn is_async(&self) -> bool {
182 self.symbol_flags().contains(SymbolFlags::IS_ASYNC)
183 }
184
185 /// Check if this symbol is a test
186 #[inline]
187 pub fn is_test(&self) -> bool {
188 self.symbol_flags().contains(SymbolFlags::IS_TEST)
189 }
190}
191
192// =============================================================================
193// TOKEN KIND ENUM
194// =============================================================================
195
196/// Classification of tokens (identifiers)
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
198#[repr(u8)]
199pub enum TokenKind {
200 /// Variable/identifier reference
201 Identifier = 0,
202 /// Function/method call
203 Call = 1,
204 /// Type annotation
205 Type = 2,
206 /// Import reference
207 Import = 3,
208 /// Property access (obj.prop)
209 Property = 4,
210 /// Decorator/attribute
211 Decorator = 5,
212 /// Label (for goto/break/continue)
213 Label = 6,
214 /// Unknown token type
215 Unknown = 255,
216}
217
218impl From<u8> for TokenKind {
219 fn from(value: u8) -> Self {
220 match value {
221 0 => Self::Identifier,
222 1 => Self::Call,
223 2 => Self::Type,
224 3 => Self::Import,
225 4 => Self::Property,
226 5 => Self::Decorator,
227 6 => Self::Label,
228 _ => Self::Unknown,
229 }
230 }
231}
232
233// =============================================================================
234// TOKEN
235// =============================================================================
236
237/// A token (identifier) occurrence in the source code
238///
239/// Layout (24 bytes):
240/// - id: u32 (4)
241/// - name_offset: u32 (4)
242/// - file_id: u16 (2)
243/// - column: u16 (2)
244/// - line: u32 (4)
245/// - kind: u8 (1)
246/// - _padding: [u8; 3] (3)
247/// - scope_id: u32 (4)
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
249#[repr(C)]
250pub struct Token {
251 /// Unique token ID
252 pub id: u32,
253 /// Offset into string table for the token name
254 pub name_offset: u32,
255 /// File ID where this token appears
256 pub file_id: u16,
257 /// Column number (0-indexed)
258 pub column: u16,
259 /// Line number (1-indexed)
260 pub line: u32,
261 /// Token kind
262 pub kind: u8,
263 /// Padding for alignment
264 _padding: [u8; 3],
265 /// Scope ID this token belongs to
266 pub scope_id: u32,
267}
268
269impl Token {
270 /// Create a new token
271 #[inline]
272 pub const fn new(
273 id: u32,
274 name_offset: u32,
275 file_id: u16,
276 line: u32,
277 column: u16,
278 kind: TokenKind,
279 scope_id: u32,
280 ) -> Self {
281 Self {
282 id,
283 name_offset,
284 file_id,
285 column,
286 line,
287 kind: kind as u8,
288 _padding: [0; 3],
289 scope_id,
290 }
291 }
292
293 /// Get the token kind
294 #[inline]
295 pub fn token_kind(&self) -> TokenKind {
296 TokenKind::from(self.kind)
297 }
298}
299
300// =============================================================================
301// REFERENCE KIND ENUM
302// =============================================================================
303
304/// Classification of symbol references
305#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
306#[repr(u8)]
307pub enum RefKind {
308 /// Read access (using the value)
309 Read = 0,
310 /// Write access (assigning to it)
311 Write = 1,
312 /// Function/method call
313 Call = 2,
314 /// Type annotation usage
315 TypeAnnotation = 3,
316 /// Import statement
317 Import = 4,
318 /// Export statement
319 Export = 5,
320 /// Inheritance (extends/implements)
321 Inheritance = 6,
322 /// Decorator/attribute usage
323 Decorator = 7,
324 /// Construction (struct literal, enum variant, new expression)
325 Construction = 8,
326 /// Unknown reference type
327 Unknown = 255,
328}
329
330impl From<u8> for RefKind {
331 fn from(value: u8) -> Self {
332 match value {
333 0 => Self::Read,
334 1 => Self::Write,
335 2 => Self::Call,
336 3 => Self::TypeAnnotation,
337 4 => Self::Import,
338 5 => Self::Export,
339 6 => Self::Inheritance,
340 7 => Self::Decorator,
341 8 => Self::Construction,
342 _ => Self::Unknown,
343 }
344 }
345}
346
347// =============================================================================
348// REFERENCE
349// =============================================================================
350
351/// A reference from a token to a symbol
352///
353/// Layout (12 bytes):
354/// - token_id: u32 (4)
355/// - symbol_id: u32 (4)
356/// - kind: u8 (1)
357/// - _padding: [u8; 3] (3)
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
359#[repr(C)]
360pub struct Reference {
361 /// Token ID that references the symbol
362 pub token_id: u32,
363 /// Symbol ID being referenced
364 pub symbol_id: u32,
365 /// Kind of reference
366 pub kind: u8,
367 /// Padding for alignment
368 _padding: [u8; 3],
369}
370
371impl Reference {
372 /// Create a new reference
373 #[inline]
374 pub const fn new(token_id: u32, symbol_id: u32, kind: RefKind) -> Self {
375 Self {
376 token_id,
377 symbol_id,
378 kind: kind as u8,
379 _padding: [0; 3],
380 }
381 }
382
383 /// Get the reference kind
384 #[inline]
385 pub fn ref_kind(&self) -> RefKind {
386 RefKind::from(self.kind)
387 }
388
389 /// Check if this is a call reference
390 #[inline]
391 pub fn is_call(&self) -> bool {
392 self.ref_kind() == RefKind::Call
393 }
394
395 /// Check if this is a read reference
396 #[inline]
397 pub fn is_read(&self) -> bool {
398 self.ref_kind() == RefKind::Read
399 }
400
401 /// Check if this is a write reference
402 #[inline]
403 pub fn is_write(&self) -> bool {
404 self.ref_kind() == RefKind::Write
405 }
406
407 /// Check if this is a construction reference
408 #[inline]
409 pub fn is_construction(&self) -> bool {
410 self.ref_kind() == RefKind::Construction
411 }
412}
413
414// =============================================================================
415// SCOPE KIND ENUM
416// =============================================================================
417
418/// Classification of scope types
419#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
420#[repr(u8)]
421pub enum ScopeKind {
422 /// File/module level scope
423 File = 0,
424 /// Module/namespace scope
425 Module = 1,
426 /// Class body scope
427 Class = 2,
428 /// Function/method body scope
429 Function = 3,
430 /// Block scope (if, for, while, etc.)
431 Block = 4,
432 /// Lambda/closure scope
433 Lambda = 5,
434 /// Comprehension scope (list comp, etc.)
435 Comprehension = 6,
436 /// Unknown scope type
437 Unknown = 255,
438}
439
440impl From<u8> for ScopeKind {
441 fn from(value: u8) -> Self {
442 match value {
443 0 => Self::File,
444 1 => Self::Module,
445 2 => Self::Class,
446 3 => Self::Function,
447 4 => Self::Block,
448 5 => Self::Lambda,
449 6 => Self::Comprehension,
450 _ => Self::Unknown,
451 }
452 }
453}
454
455// =============================================================================
456// SCOPE
457// =============================================================================
458
459/// A scope node in the scope tree
460///
461/// Layout (24 bytes):
462/// - id: u32 (4)
463/// - kind: u8 (1)
464/// - _padding1: u8 (1)
465/// - file_id: u16 (2)
466/// - parent_id: u32 (4)
467/// - start_line: u32 (4)
468/// - end_line: u32 (4)
469/// - name_offset: u32 (4)
470#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
471#[repr(C)]
472pub struct Scope {
473 /// Unique scope ID
474 pub id: u32,
475 /// Scope kind
476 pub kind: u8,
477 /// Padding for alignment
478 _padding1: u8,
479 /// File ID where this scope exists
480 pub file_id: u16,
481 /// Parent scope ID (u32::MAX for root/file scope)
482 pub parent_id: u32,
483 /// Starting line number (1-indexed)
484 pub start_line: u32,
485 /// Ending line number (1-indexed)
486 pub end_line: u32,
487 /// Offset into string table for scope name (if any)
488 pub name_offset: u32,
489}
490
491/// Sentinel value for root scope (no parent)
492pub const NO_PARENT_SCOPE: u32 = u32::MAX;
493
494impl Scope {
495 /// Create a new scope
496 #[inline]
497 pub const fn new(
498 id: u32,
499 kind: ScopeKind,
500 file_id: u16,
501 parent_id: u32,
502 start_line: u32,
503 end_line: u32,
504 name_offset: u32,
505 ) -> Self {
506 Self {
507 id,
508 kind: kind as u8,
509 _padding1: 0,
510 file_id,
511 parent_id,
512 start_line,
513 end_line,
514 name_offset,
515 }
516 }
517
518 /// Create a file-level (root) scope
519 #[inline]
520 pub const fn file_scope(id: u32, file_id: u16, end_line: u32) -> Self {
521 Self::new(
522 id,
523 ScopeKind::File,
524 file_id,
525 NO_PARENT_SCOPE,
526 1,
527 end_line,
528 0,
529 )
530 }
531
532 /// Get the scope kind
533 #[inline]
534 pub fn scope_kind(&self) -> ScopeKind {
535 ScopeKind::from(self.kind)
536 }
537
538 /// Check if this is a root/file scope
539 #[inline]
540 pub fn is_root(&self) -> bool {
541 self.parent_id == NO_PARENT_SCOPE
542 }
543
544 /// Check if this is a function scope
545 #[inline]
546 pub fn is_function(&self) -> bool {
547 self.scope_kind() == ScopeKind::Function
548 }
549}
550
551// =============================================================================
552// EDGE
553// =============================================================================
554
555/// A call graph edge from one symbol to another
556///
557/// Layout (12 bytes):
558/// - from_symbol: u32 (4)
559/// - to_symbol: u32 (4)
560/// - line: u32 (4)
561#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
562#[repr(C)]
563pub struct Edge {
564 /// Symbol ID of the caller
565 pub from_symbol: u32,
566 /// Symbol ID of the callee
567 pub to_symbol: u32,
568 /// Line number where the call occurs
569 pub line: u32,
570}
571
572impl Edge {
573 /// Create a new edge
574 #[inline]
575 pub const fn new(from_symbol: u32, to_symbol: u32, line: u32) -> Self {
576 Self {
577 from_symbol,
578 to_symbol,
579 line,
580 }
581 }
582}
583
584// =============================================================================
585// SIZE ASSERTIONS
586// =============================================================================
587
588// Compile-time size checks for mmap compatibility
589const _: () = {
590 assert!(std::mem::size_of::<Symbol>() == 24);
591 assert!(std::mem::size_of::<Token>() == 24);
592 assert!(std::mem::size_of::<Reference>() == 12);
593 assert!(std::mem::size_of::<Scope>() == 24);
594 assert!(std::mem::size_of::<Edge>() == 12);
595};
596
597// =============================================================================
598// TESTS
599// =============================================================================
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 #[test]
606 fn test_symbol_kind_roundtrip() {
607 for kind in [
608 SymbolKind::Function,
609 SymbolKind::Method,
610 SymbolKind::Class,
611 SymbolKind::Struct,
612 SymbolKind::Enum,
613 SymbolKind::Interface,
614 SymbolKind::TypeAlias,
615 SymbolKind::Constant,
616 SymbolKind::Variable,
617 SymbolKind::Module,
618 ] {
619 assert_eq!(SymbolKind::from(kind as u8), kind);
620 }
621 }
622
623 #[test]
624 fn test_symbol_flags() {
625 let flags = SymbolFlags::IS_ENTRY_POINT | SymbolFlags::IS_ASYNC;
626 assert!(flags.contains(SymbolFlags::IS_ENTRY_POINT));
627 assert!(flags.contains(SymbolFlags::IS_ASYNC));
628 assert!(!flags.contains(SymbolFlags::IS_EXPORTED));
629 }
630
631 #[test]
632 fn test_symbol_creation() {
633 let sym = Symbol::new(
634 0,
635 100,
636 1,
637 SymbolKind::Function,
638 SymbolFlags::IS_ENTRY_POINT | SymbolFlags::IS_EXPORTED,
639 10,
640 50,
641 );
642 assert_eq!(sym.id, 0);
643 assert_eq!(sym.name_offset, 100);
644 assert_eq!(sym.file_id, 1);
645 assert_eq!(sym.symbol_kind(), SymbolKind::Function);
646 assert!(sym.is_entry_point());
647 assert!(sym.is_exported());
648 assert!(!sym.is_async());
649 assert_eq!(sym.start_line, 10);
650 assert_eq!(sym.end_line, 50);
651 }
652
653 #[test]
654 fn test_reference_kind_roundtrip() {
655 for kind in [
656 RefKind::Read,
657 RefKind::Write,
658 RefKind::Call,
659 RefKind::TypeAnnotation,
660 RefKind::Import,
661 RefKind::Export,
662 RefKind::Inheritance,
663 RefKind::Decorator,
664 RefKind::Construction,
665 ] {
666 assert_eq!(RefKind::from(kind as u8), kind);
667 }
668 }
669
670 #[test]
671 fn test_scope_hierarchy() {
672 let file_scope = Scope::file_scope(0, 0, 100);
673 assert!(file_scope.is_root());
674 assert_eq!(file_scope.scope_kind(), ScopeKind::File);
675
676 let fn_scope = Scope::new(1, ScopeKind::Function, 0, 0, 10, 20, 50);
677 assert!(!fn_scope.is_root());
678 assert!(fn_scope.is_function());
679 assert_eq!(fn_scope.parent_id, 0);
680 }
681
682 #[test]
683 fn test_edge_creation() {
684 let edge = Edge::new(1, 2, 42);
685 assert_eq!(edge.from_symbol, 1);
686 assert_eq!(edge.to_symbol, 2);
687 assert_eq!(edge.line, 42);
688 }
689}