mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-15 00:19:33 +00:00
Keep every method a duplicate index key maps to, not just the last one
A plain dict assignment silently overwrote an earlier target whenever
two method nodes shared both a source impl node and a stripped
label, which would have kept whichever one was seen last instead of
surfacing the case as the ambiguity it is. The index now keeps a set
per key and folds every match into the candidate set before the
exactly one candidate check runs, so a genuine duplicate still bails
instead of guessing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2cbbf1a670)
This commit is contained in:
1 parent
4f13b17aa2
commit
58c497d2da
2 files changed
+46
-8
No files matched your search
+10
-8
@@ -4758,8 +4758,12 @@ def _resolve_rust_self_member_calls(
|
||||
if str(n.get("source_file") or "").endswith(".rs"):
|
||||
nids_by_label.setdefault(n.get("label", ""), []).append(n.get("id"))
|
||||
|
||||
# (impl/type node id, bare method name) -> method node id, from `method` edges.
|
||||
method_index: dict[tuple[str, str], str] = {}
|
||||
# (impl/type node id, bare method name) -> method node id(s), from `method`
|
||||
# edges. A set, not a single overwritten value: two distinct method nodes
|
||||
# sharing both a source and a stripped label (however unlikely) must still
|
||||
# surface as an ambiguity below rather than silently keeping whichever one
|
||||
# was seen last.
|
||||
method_index: dict[tuple[str, str], set[str]] = {}
|
||||
for e in all_edges:
|
||||
if e.get("relation") != "method":
|
||||
continue
|
||||
@@ -4767,7 +4771,7 @@ def _resolve_rust_self_member_calls(
|
||||
tnode = node_by_id.get(tgt)
|
||||
if tnode is not None:
|
||||
name = str(tnode.get("label", "")).strip("()").lstrip(".")
|
||||
method_index[(src, name)] = tgt
|
||||
method_index.setdefault((src, name), set()).add(tgt)
|
||||
|
||||
# Scoped to `calls`: a caller that already has a DIFFERENT relation to the
|
||||
# same target (e.g. a `references` edge from also naming the type in a
|
||||
@@ -4781,11 +4785,9 @@ def _resolve_rust_self_member_calls(
|
||||
for rc in raw:
|
||||
caller = rc["caller_nid"]
|
||||
callee = rc["callee"]
|
||||
candidates = {
|
||||
method_index[(nid, callee)]
|
||||
for nid in nids_by_label.get(rc["rust_self_type"], [])
|
||||
if (nid, callee) in method_index
|
||||
}
|
||||
candidates: set[str] = set()
|
||||
for nid in nids_by_label.get(rc["rust_self_type"], []):
|
||||
candidates |= method_index.get((nid, callee), set())
|
||||
if len(candidates) != 1: # zero or ambiguous -> no edge (god-node guard)
|
||||
continue
|
||||
tgt = next(iter(candidates))
|
||||
|
||||
@@ -168,3 +168,39 @@ def test_resolver_is_not_suppressed_by_an_unrelated_edge_to_the_same_pair(tmp_pa
|
||||
if e["relation"] == "calls"
|
||||
}
|
||||
assert ("impl_foo_caller", "impl_foo_method") in calls
|
||||
|
||||
|
||||
def test_resolver_treats_a_duplicate_method_index_key_as_ambiguous(tmp_path: Path):
|
||||
"""Two DIFFERENT method nodes sharing both a source impl node and a
|
||||
stripped label must surface as an ambiguity (no edge), not silently
|
||||
keep whichever one a plain dict overwrite happened to see last."""
|
||||
from graphify.extract import _resolve_rust_self_member_calls
|
||||
|
||||
all_nodes = [
|
||||
{"id": "impl_foo", "label": "Foo", "source_file": "a.rs"},
|
||||
{"id": "impl_foo_method_a", "label": ".method()", "source_file": "a.rs"},
|
||||
{"id": "impl_foo_method_b", "label": ".method()", "source_file": "a.rs"},
|
||||
{"id": "impl_foo_caller", "label": ".caller()", "source_file": "a.rs"},
|
||||
]
|
||||
all_edges = [
|
||||
{"source": "impl_foo", "target": "impl_foo_method_a", "relation": "method"},
|
||||
{"source": "impl_foo", "target": "impl_foo_method_b", "relation": "method"},
|
||||
]
|
||||
per_file = [{
|
||||
"raw_calls": [{
|
||||
"caller_nid": "impl_foo_caller",
|
||||
"callee": "method",
|
||||
"rust_self_type": "Foo",
|
||||
"source_file": "a.rs",
|
||||
"source_location": "L1",
|
||||
}],
|
||||
}]
|
||||
|
||||
_resolve_rust_self_member_calls(per_file, all_nodes, all_edges)
|
||||
|
||||
calls = {
|
||||
(e["source"], e["target"])
|
||||
for e in all_edges
|
||||
if e["relation"] == "calls"
|
||||
}
|
||||
assert not calls, "two same-labeled method nodes on one type must not guess"
|
||||
Reference in new issue
Block a user