Skip to main content

normalize_facts_rules_api/
relations.rs

1//! Relations (facts) that rules operate on.
2//!
3//! These are the inputs to Datalog rules, extracted from code by normalize-facts.
4//! Each relation type maps to a Datalog predicate:
5//!
6//! - `symbol(file, name, kind, line)` - defined symbols
7//! - `import(from_file, to_module, name)` - import statements
8//! - `call(caller_file, caller_name, callee_name, line)` - function calls
9//! - `visibility(file, name, vis)` - symbol visibility
10//! - `attribute(file, name, attr)` - symbol attributes (one per attribute)
11//! - `parent(file, child_name, parent_name)` - symbol nesting hierarchy
12//! - `qualifier(caller_file, caller_name, callee_name, qual)` - call qualifier
13//! - `symbol_range(file, name, start_line, end_line)` - symbol span
14//! - `implements(file, name, interface)` - interface/trait implementation
15//! - `is_impl(file, name)` - symbol is a trait/interface implementation
16//! - `type_method(file, type_name, method_name)` - method signatures on types
17
18/// A symbol fact: a named entity defined in a file.
19///
20/// Maps to Datalog: `symbol(file, name, kind, line)`
21#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
22#[rkyv(derive(Debug))]
23pub struct SymbolFact {
24    /// File path relative to project root
25    pub file: String,
26    /// Symbol name
27    pub name: String,
28    /// Symbol kind (function, class, method, etc.)
29    pub kind: String,
30    /// Line number where symbol is defined
31    pub line: u32,
32}
33
34/// An import fact: a dependency from one file to another module.
35///
36/// Maps to Datalog: `import(from_file, to_module, name)`
37#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
38#[rkyv(derive(Debug))]
39pub struct ImportFact {
40    /// File containing the import
41    pub from_file: String,
42    /// Raw module specifier as written in the source.
43    ///
44    /// The value depends on the language and import style:
45    ///
46    /// - **Relative or absolute file path** — e.g. `"../foo"`, `"./utils"` (JS/TS, Python
47    ///   relative imports). The path is as written in source, not resolved to an absolute path.
48    /// - **Module name** — e.g. `"os"` (Python stdlib), `"std::collections"` (Rust), `"fmt"`
49    ///   (Go). These are not file paths and cannot be resolved without a module resolver.
50    /// - **Empty string `""`** — when the grammar does not expose a module path for the
51    ///   import, or for star imports that name no explicit module (e.g. some wildcard import
52    ///   syntaxes). Callers should treat `""` as "module not known".
53    ///
54    /// Resolved file paths (when available) are stored separately in the index, not here.
55    pub module_specifier: String,
56    /// Name being imported (or "*" for wildcard)
57    pub name: String,
58}
59
60/// A call fact: a function call from one symbol to another.
61///
62/// Maps to Datalog: `call(caller_file, caller_name, callee_name, line)`
63#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
64#[rkyv(derive(Debug))]
65pub struct CallFact {
66    /// File containing the call
67    pub caller_file: String,
68    /// Name of the calling function/method
69    pub caller_name: String,
70    /// Name of the called function/method
71    pub callee_name: String,
72    /// Line number of the call
73    pub line: u32,
74}
75
76/// A visibility fact: the visibility of a symbol.
77///
78/// Maps to Datalog: `visibility(file, name, vis)`
79#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
80#[rkyv(derive(Debug))]
81pub struct VisibilityFact {
82    /// File path relative to project root
83    pub file: String,
84    /// Symbol name
85    pub name: String,
86    /// Visibility: "public", "private", "protected", "internal"
87    pub visibility: String,
88}
89
90/// An attribute fact: one attribute annotation on a symbol.
91///
92/// Maps to Datalog: `attribute(file, name, attr)`
93#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
94#[rkyv(derive(Debug))]
95pub struct AttributeFact {
96    /// File path relative to project root
97    pub file: String,
98    /// Symbol name
99    pub name: String,
100    /// Attribute string (e.g. "#[derive(Debug)]", "@Override")
101    pub attribute: String,
102}
103
104/// A parent fact: symbol nesting hierarchy.
105///
106/// Maps to Datalog: `parent(file, child_name, parent_name)`
107#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
108#[rkyv(derive(Debug))]
109pub struct ParentFact {
110    /// File path relative to project root
111    pub file: String,
112    /// Child symbol name
113    pub child_name: String,
114    /// Parent symbol name
115    pub parent_name: String,
116}
117
118/// A qualifier fact: call qualifier (receiver/module).
119///
120/// Maps to Datalog: `qualifier(caller_file, caller_name, callee_name, qual)`
121#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
122#[rkyv(derive(Debug))]
123pub struct QualifierFact {
124    /// File containing the call
125    pub caller_file: String,
126    /// Name of the calling function/method
127    pub caller_name: String,
128    /// Name of the called function/method
129    pub callee_name: String,
130    /// Qualifier ("self", module name, etc.)
131    pub qualifier: String,
132}
133
134/// A symbol range fact: start and end lines of a symbol.
135///
136/// Maps to Datalog: `symbol_range(file, name, start_line, end_line)`
137#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
138#[rkyv(derive(Debug))]
139pub struct SymbolRangeFact {
140    /// File path relative to project root
141    pub file: String,
142    /// Symbol name
143    pub name: String,
144    /// Start line number
145    pub start_line: u32,
146    /// End line number
147    pub end_line: u32,
148}
149
150/// An implements fact: a symbol implements an interface/trait.
151///
152/// Maps to Datalog: `implements(file, name, interface)`
153#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
154#[rkyv(derive(Debug))]
155pub struct ImplementsFact {
156    /// File path relative to project root
157    pub file: String,
158    /// Symbol name
159    pub name: String,
160    /// Interface/trait name
161    pub interface: String,
162}
163
164/// An is_impl fact: symbol is a trait/interface implementation.
165///
166/// Maps to Datalog: `is_impl(file, name)`
167#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
168#[rkyv(derive(Debug))]
169pub struct IsImplFact {
170    /// File path relative to project root
171    pub file: String,
172    /// Symbol name
173    pub name: String,
174}
175
176/// A type method fact: a method signature on a type.
177///
178/// Maps to Datalog: `type_method(file, type_name, method_name)`
179#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
180#[rkyv(derive(Debug))]
181pub struct TypeMethodFact {
182    /// File path relative to project root
183    pub file: String,
184    /// Type (interface/class) name
185    pub type_name: String,
186    /// Method name
187    pub method_name: String,
188}
189
190/// All relations (facts) available to rules.
191///
192/// This is the complete set of facts extracted from a codebase.
193/// Rule packs receive this and apply Datalog rules over it.
194#[derive(Clone, Debug, Default, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
195#[rkyv(derive(Debug))]
196pub struct Relations {
197    /// All symbols defined in the codebase
198    pub symbols: Vec<SymbolFact>,
199    /// All imports in the codebase
200    pub imports: Vec<ImportFact>,
201    /// All function calls in the codebase
202    pub calls: Vec<CallFact>,
203    /// Symbol visibility facts
204    pub visibilities: Vec<VisibilityFact>,
205    /// Symbol attribute facts (one per attribute per symbol)
206    pub attributes: Vec<AttributeFact>,
207    /// Symbol parent-child hierarchy
208    pub parents: Vec<ParentFact>,
209    /// Call qualifier facts (receiver/module on calls)
210    pub qualifiers: Vec<QualifierFact>,
211    /// Symbol range facts (start and end lines)
212    pub symbol_ranges: Vec<SymbolRangeFact>,
213    /// Implements facts (symbol implements interface/trait)
214    pub implements: Vec<ImplementsFact>,
215    /// Is-impl facts (symbol is a trait/interface implementation)
216    pub is_impls: Vec<IsImplFact>,
217    /// Type method facts (method signatures on types)
218    pub type_methods: Vec<TypeMethodFact>,
219}
220
221impl Relations {
222    /// Create empty relations
223    pub fn new() -> Self {
224        Self::default()
225    }
226
227    /// Add a symbol fact
228    pub fn add_symbol(&mut self, file: &str, name: &str, kind: &str, line: u32) {
229        self.symbols.push(SymbolFact {
230            file: file.into(),
231            name: name.into(),
232            kind: kind.into(),
233            line,
234        });
235    }
236
237    /// Add an import fact
238    pub fn add_import(&mut self, from_file: &str, to_module: &str, name: &str) {
239        self.imports.push(ImportFact {
240            from_file: from_file.into(),
241            module_specifier: to_module.into(),
242            name: name.into(),
243        });
244    }
245
246    /// Add a call fact
247    pub fn add_call(&mut self, caller_file: &str, caller_name: &str, callee_name: &str, line: u32) {
248        self.calls.push(CallFact {
249            caller_file: caller_file.into(),
250            caller_name: caller_name.into(),
251            callee_name: callee_name.into(),
252            line,
253        });
254    }
255
256    /// Add a visibility fact
257    pub fn add_visibility(&mut self, file: &str, name: &str, visibility: &str) {
258        self.visibilities.push(VisibilityFact {
259            file: file.into(),
260            name: name.into(),
261            visibility: visibility.into(),
262        });
263    }
264
265    /// Add an attribute fact
266    pub fn add_attribute(&mut self, file: &str, name: &str, attribute: &str) {
267        self.attributes.push(AttributeFact {
268            file: file.into(),
269            name: name.into(),
270            attribute: attribute.into(),
271        });
272    }
273
274    /// Add a parent fact
275    pub fn add_parent(&mut self, file: &str, child_name: &str, parent_name: &str) {
276        self.parents.push(ParentFact {
277            file: file.into(),
278            child_name: child_name.into(),
279            parent_name: parent_name.into(),
280        });
281    }
282
283    /// Add a qualifier fact
284    pub fn add_qualifier(
285        &mut self,
286        caller_file: &str,
287        caller_name: &str,
288        callee_name: &str,
289        qualifier: &str,
290    ) {
291        self.qualifiers.push(QualifierFact {
292            caller_file: caller_file.into(),
293            caller_name: caller_name.into(),
294            callee_name: callee_name.into(),
295            qualifier: qualifier.into(),
296        });
297    }
298
299    /// Add a symbol range fact
300    pub fn add_symbol_range(&mut self, file: &str, name: &str, start_line: u32, end_line: u32) {
301        self.symbol_ranges.push(SymbolRangeFact {
302            file: file.into(),
303            name: name.into(),
304            start_line,
305            end_line,
306        });
307    }
308
309    /// Add an implements fact
310    pub fn add_implements(&mut self, file: &str, name: &str, interface: &str) {
311        self.implements.push(ImplementsFact {
312            file: file.into(),
313            name: name.into(),
314            interface: interface.into(),
315        });
316    }
317
318    /// Add an is_impl fact
319    pub fn add_is_impl(&mut self, file: &str, name: &str) {
320        self.is_impls.push(IsImplFact {
321            file: file.into(),
322            name: name.into(),
323        });
324    }
325
326    /// Add a type method fact
327    pub fn add_type_method(&mut self, file: &str, type_name: &str, method_name: &str) {
328        self.type_methods.push(TypeMethodFact {
329            file: file.into(),
330            type_name: type_name.into(),
331            method_name: method_name.into(),
332        });
333    }
334}