diff --git a/graphify/extractors/engine.py b/graphify/extractors/engine.py index 90c95cc49..9d0305f86 100644 --- a/graphify/extractors/engine.py +++ b/graphify/extractors/engine.py @@ -2579,7 +2579,14 @@ def _php_emit_base(base_name: str, rel: str, at_line: int) -> None: "source_location": "", }) seen_ids.add(base_nid) - relation = _csharp_classify_base(base, csharp_interface_names) + # An `interface`'s base_list holds base interfaces, so every + # entry is interface inheritance (`inherits`) -- the same way the + # Java extractor treats `extends_interfaces`. Only class/struct/ + # record declarations use the name-based class-vs-interface split. + if t == "interface_declaration": + relation = "inherits" + else: + relation = _csharp_classify_base(base, csharp_interface_names) metadata = {"ref_token": base} if qualified: metadata["qualified"] = True diff --git a/tests/test_languages.py b/tests/test_languages.py index f610fbc40..718be701b 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -378,6 +378,35 @@ def test_csharp_splits_inherits_and_implements_edges(): assert ("DataProcessor", "IProcessor") in _edge_labels(result, "implements") +def test_csharp_interface_extends_is_inherits_not_implements(tmp_path): + # A C# `interface`'s base_list holds base interfaces, so extending another + # interface is interface *inheritance* -- mirroring how the Java extractor + # treats `extends_interfaces`. These edges used to be mislabeled `implements`, + # which is reserved for a class/struct/record realizing an interface. + source = tmp_path / "Interfaces.cs" + source.write_text( + "public interface IBase {}\n" + "public interface IOther {}\n" + "public interface IDerived : IBase {}\n" + "public interface IMulti : IBase, IOther {}\n" + "public class Impl : IBase {}\n" + ) + result = extract_csharp(source) + inherits = _edge_labels(result, "inherits") + implements = _edge_labels(result, "implements") + + # interface-extends-interface is inheritance, not implementation + assert ("IDerived", "IBase") in inherits + assert ("IMulti", "IBase") in inherits + assert ("IMulti", "IOther") in inherits + assert ("IDerived", "IBase") not in implements + assert ("IMulti", "IBase") not in implements + + # a class realizing an interface is still `implements` + assert ("Impl", "IBase") in implements + assert ("Impl", "IBase") not in inherits + + def test_csharp_parameter_return_and_generic_contexts(): result = extract_csharp(FIXTURES / "sample.cs") assert ("Build", "HttpClient") in _edge_labels(result, "references", "parameter_type")