From 21bcb436b58c5922e437e6abbd3dfbe95a14ad4b Mon Sep 17 00:00:00 2001 From: Synvoya <16019863+Synvoya@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:30:26 +1000 Subject: [PATCH] fix(cpp): emit generic_arg references for base-class template arguments The C++ base_class_clause handler's `template_type` branch read the base name (`sub.child_by_field_name("name")`) and emitted the `inherits` edge, but never descended into the base's `template_argument_list`. As a result `class Car : public Base` emitted `Car -> Base` (inherits) yet dropped the `Car -> Dep` generic_arg reference entirely. The Java handler `_emit_java_parent_type` already emits these generic_arg references for base-class type arguments; C++ was the asymmetric gap. Fix: after emitting the `inherits` edge, grab the base's `arguments` field (the `template_argument_list`) and run `_cpp_collect_type_refs` over each named argument with the generic flag set, emitting a `references` edge (context "generic_arg") per collected type, guarding target != class node. `_cpp_collect_type_refs` already handles nested/qualified args, so `Base>` is covered too. Adds a templated base (`Connection`) + derived class (`PooledClient : public Connection`) to tests/fixtures/sample.cpp and a test mirroring the Java generic-parents test. --- graphify/extract.py | 20 ++++++++++++++++++++ tests/fixtures/sample.cpp | 11 +++++++++++ tests/test_languages.py | 9 +++++++++ 3 files changed, 40 insertions(+) diff --git a/graphify/extract.py b/graphify/extract.py index 5a3ec2ab..9b366aba 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -3668,6 +3668,7 @@ def _extract_generic( continue for sub in child.children: base = "" + template_args_node = None if sub.type == "type_identifier": base = _read_text(sub, source) elif sub.type == "qualified_identifier": @@ -3679,6 +3680,12 @@ def _extract_generic( elif sub.type == "template_type": tname = sub.child_by_field_name("name") base = _read_text(tname, source) if tname else _read_text(sub, source) + # The base's template_argument_list carries generic + # type arguments (class Car : public Base). The + # Java handler (_emit_java_parent_type) emits these as + # generic_arg references; C++ dropped them because we + # only emitted the `inherits` edge on the base name. + template_args_node = sub.child_by_field_name("arguments") else: continue if not base: @@ -3696,6 +3703,19 @@ def _extract_generic( }) seen_ids.add(base_nid) add_edge(class_nid, base_nid, "inherits", line) + # Emit a generic_arg reference for each type argument on the + # base (Base -> Car references Dep). _cpp_collect_type_refs + # handles nested/qualified args (Base>) too. + if template_args_node is not None: + arg_refs: list[tuple[str, str]] = [] + for arg in template_args_node.children: + if arg.is_named: + _cpp_collect_type_refs(arg, source, True, arg_refs) + for ref_name, _role in arg_refs: + target_nid = ensure_named_node(ref_name, line) + if target_nid != class_nid: + add_edge(class_nid, target_nid, "references", + line, context="generic_arg") # Find body and recurse body = _find_body(node, config) diff --git a/tests/fixtures/sample.cpp b/tests/fixtures/sample.cpp index f48f8335..aa68e6ec 100644 --- a/tests/fixtures/sample.cpp +++ b/tests/fixtures/sample.cpp @@ -36,6 +36,17 @@ struct RetryingHttpClient : HttpClient { int maxRetries; }; +template +class Connection { +public: + T resource; +}; + +class PooledClient : public Connection { +public: + int poolSize; +}; + int main() { HttpClient client("https://api.example.com"); std::string response = client.get("/users"); diff --git a/tests/test_languages.py b/tests/test_languages.py index 31832052..70ae1350 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -227,6 +227,15 @@ def test_cpp_struct_inherits_edge(): assert found, "RetryingHttpClient (struct) should have inherits edge to HttpClient" +def test_cpp_generic_parents_include_type_argument_references(): + """`class PooledClient : public Connection` must emit the inherits + edge to Connection AND a generic_arg reference to the HttpClient type argument, + matching the Java base-class behaviour (_emit_java_parent_type).""" + r = extract_cpp(FIXTURES / "sample.cpp") + assert ("PooledClient", "Connection") in _edge_labels(r, "inherits") + assert ("PooledClient", "HttpClient") in _edge_labels(r, "references", "generic_arg") + + # ── CUDA ────────────────────────────────────────────────────────────────────── # CUDA is a C++ superset, so .cu/.cuh route through the C++ (tree-sitter-cpp) # extractor. These tests guard that __global__/__device__ kernels, host