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