From 58a6efcfe375a7be84cb792e2bf1d338de2e3ddf Mon Sep 17 00:00:00 2001 From: Synvoya <16019863+Synvoya@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:31:47 +1000 Subject: [PATCH] fix(kotlin): emit implements edge for interface delegation (`by`) class Foo : Bar by baz produced no edge because the delegation_specifier loop only handled constructor_invocation and bare user_type children; the by form wraps user_type in an explicit_delegation node. Add that branch so the implements edge (and generic-arg recovery) fires. --- graphify/extract.py | 11 +++++++++++ tests/fixtures/sample.kt | 2 ++ tests/test_languages.py | 7 +++++++ 3 files changed, 20 insertions(+) diff --git a/graphify/extract.py b/graphify/extract.py index f1cb548d6..db1cebaaa 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -3648,6 +3648,17 @@ def _php_emit_base(base_name: str, rel: str, at_line: int) -> None: if sub.type == "user_type": user_type_node = sub break + # `class Foo : Bar by baz` wraps the delegated + # interface `Bar` in an `explicit_delegation` + # node; grab its first `user_type` descendant so + # the implements edge (and generic-arg recovery) + # still fire. + if sub.type == "explicit_delegation": + for inner in sub.children: + if inner.type == "user_type": + user_type_node = inner + break + break if user_type_node is None: continue base = _kotlin_user_type_name(user_type_node, source) diff --git a/tests/fixtures/sample.kt b/tests/fixtures/sample.kt index 5c4489c5d..db3f2810d 100644 --- a/tests/fixtures/sample.kt +++ b/tests/fixtures/sample.kt @@ -35,6 +35,8 @@ class DataProcessor : BaseProcessor(), Loggable { override fun log() {} } +class LoggingList(inner: MutableList) : MutableList by inner + fun createClient(baseUrl: String): HttpClient { val config = Config(baseUrl, 30) return HttpClient(config) diff --git a/tests/test_languages.py b/tests/test_languages.py index ca5169d54..588b42a2f 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -623,6 +623,13 @@ def test_kotlin_splits_inherits_and_implements(): assert ("DataProcessor", "Loggable") in _edge_labels(r, "implements") +def test_kotlin_interface_delegation_emits_implements(): + """`class Foo : Bar by baz` wraps the delegated interface in an + `explicit_delegation` node — it must still emit an implements edge.""" + r = extract_kotlin(FIXTURES / "sample.kt") + assert ("LoggingList", "MutableList") in _edge_labels(r, "implements") + + def test_kotlin_parameter_return_generic_and_field_contexts(): r = extract_kotlin(FIXTURES / "sample.kt") assert ("run", "DataProcessor") in _edge_labels(r, "references", "parameter_type")