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}