From 9aa9d03aa58a194439b67cc8501d1e333cb00534 Mon Sep 17 00:00:00 2001 From: Kamal Pandey <27366496+Goodkamalpandey@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:32:36 -0700 Subject: [PATCH] fix(dispatch): don't count gate-failing implementers toward confidence _dispatch_confidence was computed from the raw implementers set before the per-implementer language gate ran, so a sourceless/external-reference implementer, or a cross-language name collision, that never produces an edge could still dilute the confidence score for the implementer(s) that do. A single real implementer alongside one gate-failing entry scored 0.75 (the two-implementer tier) instead of 0.85. Gate first, then count only the implementers that pass it, so the score reflects how many implementers actually share the dispatch edge. Addresses review feedback on PR #4396 (Graphify review bot, finding at interface_dispatch.py:154). Co-Authored-By: Claude Sonnet 5 (cherry picked from commit b01a6fcf10be535e83e233b2f433f5914471367b) --- graphify/interface_dispatch.py | 19 +++++++++------ tests/test_csharp_interface_dispatch.py | 31 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/graphify/interface_dispatch.py b/graphify/interface_dispatch.py index f77253745..172f5d28e 100644 --- a/graphify/interface_dispatch.py +++ b/graphify/interface_dispatch.py @@ -150,17 +150,22 @@ def resolve_interface_dispatch( interface_methods = methods_of.get(interface_nid, {}) if not interface_methods: continue - score = _dispatch_confidence(len(impls)) + # Gate first, then count: an `implements` edge from a sourceless stub + # (a dangling/external reference) or a cross-language name collision + # never produces an edge below, so it must not count toward "how many + # implementers is this split between" either — that would understate + # confidence for the implementers that actually get a dispatch edge. + gated_impls: list[tuple[str, dict]] = [] for impl_nid in impls: impl_node = node_by_id.get(impl_nid) - # Both ends must be declarations in this language's files. A - # sourceless stub minted for a dangling reference carries no - # members worth dispatching to, and a cross-language pair is a - # name collision rather than an implementation. - if impl_node is None or not declared_in(impl_node, suffixes): - continue + if impl_node is not None and declared_in(impl_node, suffixes): + gated_impls.append((impl_nid, impl_node)) + if not gated_impls: + continue + score = _dispatch_confidence(len(gated_impls)) + for impl_nid, impl_node in gated_impls: impl_methods = methods_of.get(impl_nid, {}) for name, declared in interface_methods.items(): if len(declared) != 1: diff --git a/tests/test_csharp_interface_dispatch.py b/tests/test_csharp_interface_dispatch.py index 4bae67b02..d5f42636f 100644 --- a/tests/test_csharp_interface_dispatch.py +++ b/tests/test_csharp_interface_dispatch.py @@ -266,6 +266,37 @@ def test_a_non_csharp_implementer_is_not_dispatched_to(tmp_path): assert not dispatch +def test_a_gate_failing_implementer_does_not_dilute_confidence(tmp_path): + # `implementers[iface]` holds every `implements` source regardless of + # language or whether the node even resolved (a dangling/external + # reference stub). Only `A` passes the gate and gets an edge; the stub + # must not count toward "how many implementers" for confidence purposes, + # or a single real implementer would score as if it had a sibling. + from graphify.csharp_dispatch import resolve_csharp_interface_dispatch + + nodes = [ + {"id": "iface", "label": "IR", "source_file": "a.cs", "_callable_class": True}, + {"id": "a", "label": "A", "source_file": "a.cs", "_callable_class": True}, + {"id": "stub", "label": "Stub", "_callable_class": True}, # sourceless: fails declared_in + {"id": "iface_m", "label": ".M()", "source_file": "a.cs", "_callable": True}, + {"id": "a_m", "label": ".M()", "source_file": "a.cs", "_callable": True}, + {"id": "stub_m", "label": ".M()", "_callable": True}, + ] + edges = [ + {"source": "a", "target": "iface", "relation": "implements"}, + {"source": "stub", "target": "iface", "relation": "implements"}, + {"source": "iface", "target": "iface_m", "relation": "method"}, + {"source": "a", "target": "a_m", "relation": "method"}, + {"source": "stub", "target": "stub_m", "relation": "method"}, + ] + resolve_csharp_interface_dispatch([], nodes, edges) + dispatch_edges = [e for e in edges if e["relation"] == "dispatches_to"] + assert len(dispatch_edges) == 1 + assert dispatch_edges[0]["source"] == "iface_m" + assert dispatch_edges[0]["target"] == "a_m" + assert dispatch_edges[0]["confidence_score"] == 0.85 + + def test_mixed_corpus_links_only_the_csharp_pair(tmp_path): dispatch, r = _extract(tmp_path, { "IReport.cs": "public interface IReport { void Build(); }\n",