mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-23 05:55:54 +00:00
fix Rust cross-crate spurious INFERRED edges: skip scoped_identifier and trait-method blocklist from raw_calls (#908)
This commit is contained in:
+17
-1
@@ -3610,6 +3610,17 @@ def extract_go(path: Path) -> dict:
|
||||
|
||||
# ── Rust extractor (custom walk) ──────────────────────────────────────────────
|
||||
|
||||
# Common Rust trait/stdlib method names that appear in virtually every codebase.
|
||||
# Resolving these cross-file produces spurious INFERRED edges across crate
|
||||
# boundaries (issue #908) — skip them from the unresolved-call queue entirely.
|
||||
_RUST_TRAIT_METHOD_BLOCKLIST: frozenset[str] = frozenset({
|
||||
"new", "default", "parse", "from_str", "now", "clone", "into", "from",
|
||||
"to_string", "to_owned", "len", "is_empty", "iter", "next", "build",
|
||||
"start", "run", "init", "app", "get", "set", "push", "pop", "insert",
|
||||
"remove", "contains", "collect", "map", "filter", "unwrap", "expect",
|
||||
"ok", "err", "some", "none", "send", "recv", "lock", "read", "write",
|
||||
})
|
||||
|
||||
def extract_rust(path: Path) -> dict:
|
||||
"""Extract functions, structs, enums, traits, impl methods, and use declarations from a .rs file."""
|
||||
try:
|
||||
@@ -3740,6 +3751,7 @@ def extract_rust(path: Path) -> dict:
|
||||
func_node = node.child_by_field_name("function")
|
||||
callee_name: str | None = None
|
||||
is_member_call: bool = False
|
||||
is_scoped_call: bool = False
|
||||
if func_node:
|
||||
if func_node.type == "identifier":
|
||||
callee_name = _read_text(func_node, source)
|
||||
@@ -3749,6 +3761,10 @@ def extract_rust(path: Path) -> dict:
|
||||
if field:
|
||||
callee_name = _read_text(field, source)
|
||||
elif func_node.type == "scoped_identifier":
|
||||
# Type::method() — still allow in-file EXTRACTED match, but
|
||||
# skip cross-file resolution: bare last-segment lookup ignores
|
||||
# crate boundaries and produces spurious INFERRED edges (#908).
|
||||
is_scoped_call = True
|
||||
name = func_node.child_by_field_name("name")
|
||||
if name:
|
||||
callee_name = _read_text(name, source)
|
||||
@@ -3769,7 +3785,7 @@ def extract_rust(path: Path) -> dict:
|
||||
"source_location": f"L{line}",
|
||||
"weight": 1.0,
|
||||
})
|
||||
else:
|
||||
elif not is_scoped_call and callee_name.lower() not in _RUST_TRAIT_METHOD_BLOCKLIST:
|
||||
raw_calls.append({
|
||||
"caller_nid": caller_nid,
|
||||
"callee": callee_name,
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "crate_a"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
pub fn start() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> u32 {
|
||||
s.len() as u32
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "crate_b"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
// crate_b has no dependency on crate_a.
|
||||
// These calls use Type::method() (scoped_identifier) and common names that
|
||||
// previously produced spurious INFERRED edges into crate_a (#908).
|
||||
|
||||
pub struct Server;
|
||||
|
||||
impl Server {
|
||||
pub fn run(&self) {
|
||||
// Server::start() — scoped call, should not wire to crate_a::start
|
||||
let _ = Server::start();
|
||||
// Url::parse() — scoped call, should not wire to crate_a::parse
|
||||
let _ = Url::parse("http://example.com");
|
||||
}
|
||||
|
||||
fn start() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -177,6 +177,27 @@ def test_rust_no_dangling_edges():
|
||||
assert e["source"] in node_ids
|
||||
|
||||
|
||||
def test_rust_no_cross_crate_spurious_edges():
|
||||
"""Scoped calls (Type::method) and blocklisted names must not produce
|
||||
INFERRED cross-crate calls edges (#908)."""
|
||||
from graphify.extract import extract
|
||||
crate_a = FIXTURES / "crate_a" / "src" / "lib.rs"
|
||||
crate_b = FIXTURES / "crate_b" / "src" / "lib.rs"
|
||||
r = extract([crate_a, crate_b])
|
||||
node_ids_a = {n["id"] for n in r["nodes"] if "crate_a" in (n.get("source_file") or "")}
|
||||
node_ids_b = {n["id"] for n in r["nodes"] if "crate_b" in (n.get("source_file") or "")}
|
||||
# No calls edge should cross from crate_b into crate_a
|
||||
cross_crate_calls = [
|
||||
e for e in r["edges"]
|
||||
if e["relation"] == "calls"
|
||||
and e["source"] in node_ids_b
|
||||
and e["target"] in node_ids_a
|
||||
]
|
||||
assert cross_crate_calls == [], (
|
||||
f"Spurious cross-crate edges: {cross_crate_calls}"
|
||||
)
|
||||
|
||||
|
||||
# ── extract() dispatch ────────────────────────────────────────────────────────
|
||||
|
||||
def test_extract_dispatches_all_languages():
|
||||
|
||||
Reference in New Issue
Block a user