mago_codex/signature.rs
1use mago_word::Word;
2
3/// Represents a signature node for a definition (function, class, method, constant, etc.).
4///
5/// This structure forms a hierarchical tree where top-level symbols (classes, functions)
6/// can have children (methods, properties within classes).
7///
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct DefSignatureNode {
11 /// The name of the symbol (e.g., "Foo" for class Foo, "bar" for method bar)
12 pub name: Word,
13
14 /// Whether this node represents a function or method
15 pub is_function: bool,
16
17 /// Whether this node represents a constant
18 pub is_constant: bool,
19
20 /// Starting byte offset in the source file
21 pub start_offset: u32,
22
23 /// Ending byte offset in the source file
24 pub end_offset: u32,
25
26 /// Starting line number (1-indexed)
27 pub start_line: u32,
28
29 /// Ending line number (1-indexed)
30 pub end_line: u32,
31
32 /// Starting column (0-indexed)
33 pub start_column: u16,
34
35 /// Ending column (0-indexed)
36 pub end_column: u16,
37
38 /// Nested symbols (e.g., methods and properties within a class)
39 pub children: Vec<DefSignatureNode>,
40
41 /// Position-insensitive fingerprint hash covering the entire definition.
42 /// Any change to signature, body, modifiers, or attributes will change this hash.
43 pub hash: u64,
44
45 /// Signature-only fingerprint hash, excluding function/method bodies.
46 /// Used by the differ to determine cascade invalidation: if only the body changed
47 /// (signature_hash unchanged), dependents are not invalidated — only the changed
48 /// file itself is re-analyzed.
49 pub signature_hash: u64,
50}
51
52impl DefSignatureNode {
53 /// Creates a new `DefSignatureNode` with the given parameters.
54 #[inline]
55 #[allow(clippy::too_many_arguments)]
56 #[must_use]
57 pub fn new(
58 name: Word,
59 is_function: bool,
60 is_constant: bool,
61 start_offset: u32,
62 end_offset: u32,
63 start_line: u32,
64 end_line: u32,
65 start_column: u16,
66 end_column: u16,
67 hash: u64,
68 signature_hash: u64,
69 ) -> Self {
70 Self {
71 name,
72 is_function,
73 is_constant,
74 start_offset,
75 end_offset,
76 start_line,
77 end_line,
78 start_column,
79 end_column,
80 children: Vec::new(),
81 hash,
82 signature_hash,
83 }
84 }
85
86 /// Adds a child node to this definition.
87 #[inline]
88 pub fn add_child(&mut self, child: DefSignatureNode) {
89 self.children.push(child);
90 }
91
92 /// Returns a reference to the children of this node.
93 #[inline]
94 #[must_use]
95 pub fn children(&self) -> &[DefSignatureNode] {
96 &self.children
97 }
98
99 /// Returns a mutable reference to the children of this node.
100 #[inline]
101 pub fn children_mut(&mut self) -> &mut Vec<DefSignatureNode> {
102 &mut self.children
103 }
104}
105
106/// Represents the signature of an entire file.
107///
108/// This contains all top-level definitions (classes, interfaces, traits, enums,
109/// functions, constants) in the file as a flat vector. Nested definitions
110/// (methods, properties) are stored within the `children` of their parent nodes.
111#[derive(Debug, Clone, PartialEq, Eq, Default)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub struct FileSignature {
114 pub hash: u64,
115 pub ast_nodes: Vec<DefSignatureNode>,
116}
117
118impl FileSignature {
119 /// Creates a new empty `FileSignature`.
120 #[inline]
121 #[must_use]
122 pub fn new(hash: u64) -> Self {
123 Self { hash, ast_nodes: Vec::new() }
124 }
125
126 /// Adds a top-level definition node to this file signature.
127 #[inline]
128 pub fn add_node(&mut self, node: DefSignatureNode) {
129 self.ast_nodes.push(node);
130 }
131
132 /// Returns a reference to the top-level nodes.
133 #[inline]
134 #[must_use]
135 pub fn nodes(&self) -> &[DefSignatureNode] {
136 &self.ast_nodes
137 }
138
139 /// Returns a mutable reference to the top-level nodes.
140 #[inline]
141 pub fn nodes_mut(&mut self) -> &mut Vec<DefSignatureNode> {
142 &mut self.ast_nodes
143 }
144
145 /// Returns true if this file signature has no nodes.
146 #[inline]
147 #[must_use]
148 pub fn is_empty(&self) -> bool {
149 self.ast_nodes.is_empty()
150 }
151
152 /// Returns the number of top-level nodes.
153 #[inline]
154 #[must_use]
155 pub fn len(&self) -> usize {
156 self.ast_nodes.len()
157 }
158}