Skip to main content

codemem_engine/index/
symbol.rs

1//! Symbol and Reference types extracted from source code via tree-sitter.
2
3use serde::{Deserialize, Serialize};
4
5/// A function/method parameter.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Parameter {
8    /// Parameter name.
9    pub name: String,
10    /// Type annotation, if present.
11    pub type_annotation: Option<String>,
12    /// Default value expression, if present.
13    pub default_value: Option<String>,
14}
15
16/// A code symbol extracted from source.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Symbol {
19    /// Simple name (e.g., "add").
20    pub name: String,
21    /// Fully qualified name (e.g., "module::Struct::method").
22    pub qualified_name: String,
23    /// What kind of symbol this is.
24    pub kind: SymbolKind,
25    /// Full signature text (up to the opening brace or the whole item for short items).
26    pub signature: String,
27    /// Visibility of the symbol.
28    pub visibility: Visibility,
29    /// File path where the symbol is defined.
30    pub file_path: String,
31    /// 0-based starting line number.
32    pub line_start: usize,
33    /// 0-based ending line number.
34    pub line_end: usize,
35    /// Documentation comment, if any (e.g., `///` or `//!` in Rust).
36    pub doc_comment: Option<String>,
37    /// Qualified name of the parent symbol (e.g., struct name for a method).
38    pub parent: Option<String>,
39    /// Extracted parameters for functions/methods.
40    #[serde(default)]
41    pub parameters: Vec<Parameter>,
42    /// Return type annotation, if present.
43    #[serde(default)]
44    pub return_type: Option<String>,
45    /// Whether this is an async function/method.
46    #[serde(default)]
47    pub is_async: bool,
48    /// Attributes, decorators, or annotations (e.g., `#[derive(Debug)]`, `@Override`).
49    #[serde(default)]
50    pub attributes: Vec<String>,
51    /// Error/exception types this symbol can throw.
52    #[serde(default)]
53    pub throws: Vec<String>,
54    /// Generic type parameters (e.g., `<T: Display>`).
55    #[serde(default)]
56    pub generic_params: Option<String>,
57    /// Whether this is an abstract method (trait/interface method without body).
58    #[serde(default)]
59    pub is_abstract: bool,
60}
61
62/// The kind of a code symbol.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum SymbolKind {
65    Function,
66    Method,
67    Class,
68    Struct,
69    Enum,
70    Interface, // trait in Rust
71    Type,      // type alias
72    Constant,
73    Module,
74    Test,
75    Field,       // struct/class field
76    Property,    // getter/setter property
77    Constructor, // __init__, constructor, new
78    EnumVariant, // individual enum variant/member
79    Macro,       // macro_rules!, C preprocessor macro
80    Decorator,   // Python decorator, Java annotation definition
81}
82
83impl std::fmt::Display for SymbolKind {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        match self {
86            Self::Function => write!(f, "function"),
87            Self::Method => write!(f, "method"),
88            Self::Class => write!(f, "class"),
89            Self::Struct => write!(f, "struct"),
90            Self::Enum => write!(f, "enum"),
91            Self::Interface => write!(f, "interface"),
92            Self::Type => write!(f, "type"),
93            Self::Constant => write!(f, "constant"),
94            Self::Module => write!(f, "module"),
95            Self::Test => write!(f, "test"),
96            Self::Field => write!(f, "field"),
97            Self::Property => write!(f, "property"),
98            Self::Constructor => write!(f, "constructor"),
99            Self::EnumVariant => write!(f, "enum_variant"),
100            Self::Macro => write!(f, "macro"),
101            Self::Decorator => write!(f, "decorator"),
102        }
103    }
104}
105
106/// Visibility of a symbol.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108pub enum Visibility {
109    Public,
110    Private,
111    Crate,     // pub(crate) in Rust
112    Protected, // for languages that have it
113}
114
115impl std::fmt::Display for Visibility {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        match self {
118            Self::Public => write!(f, "public"),
119            Self::Private => write!(f, "private"),
120            Self::Crate => write!(f, "crate"),
121            Self::Protected => write!(f, "protected"),
122        }
123    }
124}
125
126/// A reference from one symbol/location to another symbol.
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct Reference {
129    /// Qualified name of the symbol that contains this reference.
130    pub source_qualified_name: String,
131    /// Name of the referenced target (may be unresolved / simple name).
132    pub target_name: String,
133    /// What kind of reference this is.
134    pub kind: ReferenceKind,
135    /// File path where the reference occurs.
136    pub file_path: String,
137    /// 0-based line number of the reference.
138    pub line: usize,
139}
140
141/// The kind of a reference between symbols.
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
143pub enum ReferenceKind {
144    Call,
145    Import,
146    Inherits,
147    Implements,
148    TypeUsage,
149}
150
151impl std::str::FromStr for SymbolKind {
152    type Err = String;
153
154    fn from_str(s: &str) -> Result<Self, Self::Err> {
155        match s {
156            "function" => Ok(SymbolKind::Function),
157            "method" => Ok(SymbolKind::Method),
158            "class" => Ok(SymbolKind::Class),
159            "struct" => Ok(SymbolKind::Struct),
160            "enum" => Ok(SymbolKind::Enum),
161            "interface" => Ok(SymbolKind::Interface),
162            "type" => Ok(SymbolKind::Type),
163            "constant" => Ok(SymbolKind::Constant),
164            "module" => Ok(SymbolKind::Module),
165            "test" => Ok(SymbolKind::Test),
166            "field" => Ok(SymbolKind::Field),
167            "property" => Ok(SymbolKind::Property),
168            "constructor" => Ok(SymbolKind::Constructor),
169            "enum_variant" => Ok(SymbolKind::EnumVariant),
170            "macro" => Ok(SymbolKind::Macro),
171            "decorator" => Ok(SymbolKind::Decorator),
172            _ => Err(format!("Unknown SymbolKind: {s}")),
173        }
174    }
175}
176
177/// Parse a `SymbolKind` from its `Display` string (e.g. `"function"`, `"enum_variant"`).
178pub fn symbol_kind_from_str(s: &str) -> Option<SymbolKind> {
179    s.parse().ok()
180}
181
182/// Parse a `Visibility` from its `Display` string.
183pub fn visibility_from_str(s: &str) -> Visibility {
184    match s {
185        "public" => Visibility::Public,
186        "crate" => Visibility::Crate,
187        "protected" => Visibility::Protected,
188        _ => Visibility::Private,
189    }
190}
191
192/// Lossy fallback: map a `NodeKind` back to a `SymbolKind`.
193/// Several `SymbolKind` variants collapse into the same `NodeKind`, so this
194/// mapping is not lossless (e.g. `NodeKind::Constant` could be Field/Property/
195/// EnumVariant/Constant). Prefer `symbol_kind_from_str` with the stored
196/// `"symbol_kind"` payload field for lossless round-trips.
197fn symbol_kind_from_node_kind(nk: &codemem_core::NodeKind) -> SymbolKind {
198    match nk {
199        codemem_core::NodeKind::Function => SymbolKind::Function,
200        codemem_core::NodeKind::Method => SymbolKind::Method,
201        codemem_core::NodeKind::Class => SymbolKind::Class,
202        codemem_core::NodeKind::Interface => SymbolKind::Interface,
203        codemem_core::NodeKind::Type => SymbolKind::Type,
204        codemem_core::NodeKind::Constant => SymbolKind::Constant,
205        codemem_core::NodeKind::Module => SymbolKind::Module,
206        codemem_core::NodeKind::Test => SymbolKind::Test,
207        _ => SymbolKind::Function, // safe fallback for non-symbol node kinds
208    }
209}
210
211/// Reconstruct a `Symbol` from a persisted `sym:*` `GraphNode`.
212///
213/// Returns `None` if the node ID doesn't start with `"sym:"` or required
214/// payload fields are missing.
215pub fn symbol_from_graph_node(node: &codemem_core::GraphNode) -> Option<Symbol> {
216    let qualified_name = node.id.strip_prefix("sym:")?.to_string();
217
218    // Lossless kind from payload, lossy fallback from NodeKind
219    let kind = node
220        .payload
221        .get("symbol_kind")
222        .and_then(|v| v.as_str())
223        .and_then(symbol_kind_from_str)
224        .unwrap_or_else(|| symbol_kind_from_node_kind(&node.kind));
225
226    let file_path = node
227        .payload
228        .get("file_path")
229        .and_then(|v| v.as_str())
230        .unwrap_or("")
231        .to_string();
232
233    let signature = node
234        .payload
235        .get("signature")
236        .and_then(|v| v.as_str())
237        .unwrap_or("")
238        .to_string();
239
240    let visibility = node
241        .payload
242        .get("visibility")
243        .and_then(|v| v.as_str())
244        .map(visibility_from_str)
245        .unwrap_or(Visibility::Private);
246
247    let line_start = node
248        .payload
249        .get("line_start")
250        .and_then(|v| v.as_u64())
251        .unwrap_or(0) as usize;
252
253    let line_end = node
254        .payload
255        .get("line_end")
256        .and_then(|v| v.as_u64())
257        .unwrap_or(0) as usize;
258
259    let doc_comment = node
260        .payload
261        .get("doc_comment")
262        .and_then(|v| v.as_str())
263        .map(|s| s.to_string());
264
265    // Derive simple name from qualified name (last segment after "::")
266    let name = qualified_name
267        .rsplit("::")
268        .next()
269        .unwrap_or(&qualified_name)
270        .to_string();
271
272    let parameters: Vec<Parameter> = node
273        .payload
274        .get("parameters")
275        .and_then(|v| serde_json::from_value(v.clone()).ok())
276        .unwrap_or_default();
277
278    let return_type = node
279        .payload
280        .get("return_type")
281        .and_then(|v| v.as_str())
282        .map(|s| s.to_string());
283
284    let is_async = node
285        .payload
286        .get("is_async")
287        .and_then(|v| v.as_bool())
288        .unwrap_or(false);
289
290    let attributes: Vec<String> = node
291        .payload
292        .get("attributes")
293        .and_then(|v| serde_json::from_value(v.clone()).ok())
294        .unwrap_or_default();
295
296    let throws: Vec<String> = node
297        .payload
298        .get("throws")
299        .and_then(|v| serde_json::from_value(v.clone()).ok())
300        .unwrap_or_default();
301
302    let generic_params = node
303        .payload
304        .get("generic_params")
305        .and_then(|v| v.as_str())
306        .map(|s| s.to_string());
307
308    let is_abstract = node
309        .payload
310        .get("is_abstract")
311        .and_then(|v| v.as_bool())
312        .unwrap_or(false);
313
314    let parent = node
315        .payload
316        .get("parent")
317        .and_then(|v| v.as_str())
318        .map(|s| s.to_string());
319
320    Some(Symbol {
321        name,
322        qualified_name,
323        kind,
324        signature,
325        visibility,
326        file_path,
327        line_start,
328        line_end,
329        doc_comment,
330        parent,
331        parameters,
332        return_type,
333        is_async,
334        attributes,
335        throws,
336        generic_params,
337        is_abstract,
338    })
339}
340
341impl From<SymbolKind> for codemem_core::NodeKind {
342    fn from(kind: SymbolKind) -> Self {
343        match kind {
344            SymbolKind::Function => codemem_core::NodeKind::Function,
345            SymbolKind::Method => codemem_core::NodeKind::Method,
346            SymbolKind::Class => codemem_core::NodeKind::Class,
347            SymbolKind::Struct => codemem_core::NodeKind::Class,
348            SymbolKind::Enum => codemem_core::NodeKind::Class,
349            SymbolKind::Interface => codemem_core::NodeKind::Interface,
350            SymbolKind::Type => codemem_core::NodeKind::Type,
351            SymbolKind::Constant => codemem_core::NodeKind::Constant,
352            SymbolKind::Module => codemem_core::NodeKind::Module,
353            SymbolKind::Test => codemem_core::NodeKind::Test,
354            SymbolKind::Field => codemem_core::NodeKind::Constant,
355            SymbolKind::Property => codemem_core::NodeKind::Constant,
356            SymbolKind::Constructor => codemem_core::NodeKind::Method,
357            SymbolKind::EnumVariant => codemem_core::NodeKind::Constant,
358            SymbolKind::Macro => codemem_core::NodeKind::Function,
359            SymbolKind::Decorator => codemem_core::NodeKind::Function,
360        }
361    }
362}
363
364impl std::fmt::Display for ReferenceKind {
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366        match self {
367            Self::Call => write!(f, "call"),
368            Self::Import => write!(f, "import"),
369            Self::Inherits => write!(f, "inherits"),
370            Self::Implements => write!(f, "implements"),
371            Self::TypeUsage => write!(f, "type_usage"),
372        }
373    }
374}