fix(rust): walk a const/static initializer scoped to the declared symbol (#693 for Rust)

`const_item`/`static_item` ride extractVariable's generic fallback, which
minted the node and stopped — the initializer was never walked, so every call
inside it was missing from the graph:

    const LEN: usize = compute_len();                        // no caller
    static REGISTRY: Lazy<Cfg> = Lazy::new(|| build_cfg());  // no caller

That second shape is how once_cell/lazy_static singletons, handler tables and
static configs are written, so whatever they build looked unreferenced.

Fix: walk the `value` field through visitFunctionBody with the declared symbol
pushed on the scope stack. The fallback is shared, so the walk is gated to
Rust — the other languages on it spell their initializer differently and get
their own turn. The declared symbol is identified by the `name` FIELD, so the
phantom node the fallback also mints for a bare-identifier initializer
(`const MAX: u32 = OTHER;` → `MAX` plus a spurious `OTHER`) is left exactly as
it is: a separate defect, deliberately not folded in.

Measured on three Rust projects (gnss_rust, emmc-reader-gui,
bot_predlogka_rust — 282 files): 0 lost refs, 0 re-attributed, +86 new; node
and edge counts unchanged. Kernel parity holds (226/226 non-deferred files
byte-identical).
This commit is contained in:
danusha2345
2026-08-05 23:29:28 +03:00
parent 8c35774d63
commit 53ffd3962e
5 changed files with 69 additions and 2 deletions
+23 -1
View File
@@ -650,6 +650,8 @@ impl<'t> Walker<'t> {
/// and the initializer value is never body-walked.
fn extract_variable(&mut self, node: Node<'t>) {
let docstring = preceding_docstring(node, self.src);
let name_field = node.child_by_field_name("name");
let mut declared: Option<(u32, String)> = None;
for i in 0..node.named_child_count() {
let Some(child) = node.named_child(i) else { continue };
if child.kind() != "identifier" {
@@ -657,7 +659,7 @@ impl<'t> Walker<'t> {
}
let name = self.text(child).to_string();
if !name.is_empty() {
self.create_node(
let row = self.create_node(
"variable",
&name,
child,
@@ -667,6 +669,26 @@ impl<'t> Walker<'t> {
..Extra::default()
},
);
if let (Some(row), Some(nf)) = (row, name_field) {
if child.start_byte() == nf.start_byte() {
declared = Some((row, name));
}
}
}
}
// Walk the initializer ATTRIBUTED to the declared symbol (#693):
// `const N: usize = compute()` and
// `static REGISTRY: Lazy<T> = Lazy::new(|| build())` dropped every call
// inside the initializer, so a handler table or a lazily-built
// singleton linked to nothing.
if let Some(value) = node.child_by_field_name("value") {
match declared {
Some((row, name)) => {
self.stack.push(Scope { row, kind: "variable", name });
self.visit_function_body(value);
self.stack.pop();
}
None => self.visit_function_body(value),
}
}
}