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}