From 81b0f9e355e2480b4d20c8255a4e12feddd6dde1 Mon Sep 17 00:00:00 2001 From: nia-sg-bot Date: Wed, 26 Aug 2026 21:03:34 +0530 Subject: [PATCH 1/3] feat(structural): extract async Python declarations --- diffgraph/structural.py | 12 ++++++++++-- tests/test_structural.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/diffgraph/structural.py b/diffgraph/structural.py index 96f0201..31d05a5 100644 --- a/diffgraph/structural.py +++ b/diffgraph/structural.py @@ -180,7 +180,11 @@ def identifiers(node) -> set: def visit(node, parents: Tuple[Tuple[str, str], ...] = ()) -> None: next_parents = parents - if node.type in ("class_definition", "function_definition"): + if node.type in ( + "class_definition", + "function_definition", + "async_function_definition", + ): name_node = _name_child(node) if name_node is not None: name = _node_text(content, name_node) @@ -267,7 +271,11 @@ def visit(node, parents: Tuple[Tuple[str, str], ...] = ()) -> None: caller = parents[-1][0] if parents else None ancestor = node.parent while ancestor is not None: - if ancestor.type in ("class_definition", "function_definition"): + if ancestor.type in ( + "class_definition", + "function_definition", + "async_function_definition", + ): body = ancestor.child_by_field_name("body") if body is not None and not ( body.start_byte <= node.start_byte diff --git a/tests/test_structural.py b/tests/test_structural.py index 16923b2..a3425aa 100644 --- a/tests/test_structural.py +++ b/tests/test_structural.py @@ -720,6 +720,43 @@ def test_methods_nested_functions_and_deleted_imports_are_not_overclaimed(tmp_pa ) +def test_async_python_functions_are_structural_symbols_and_callers(tmp_path): + """Async declarations must be represented like their synchronous peers.""" + root = repo(tmp_path) + write( + root, + "service.py", + "async def helper():\n return 1\n\n" + "class Service:\n" + " async def run(self):\n" + " return await helper()\n", + ) + commit(root) + write( + root, + "service.py", + "async def helper():\n return 2\n\n" + "class Service:\n" + " async def run(self):\n" + " return await helper()\n", + ) + + artifact = analyze_local_diff(str(root)) + + assert_valid(artifact) + symbols = {item["id"]: item for item in artifact["symbols"]} + assert symbols["sym::service.py::helper"]["kind"] == "function" + assert symbols["sym::service.py::helper"]["change_kind"] == "modified" + assert symbols["sym::service.py::Service.run"]["kind"] == "method" + assert symbols["sym::service.py::Service.run"]["parent_id"] == "sym::service.py::Service" + relationships = { + (item["kind"], item["source_id"], item["target_id"]) + for item in artifact["relationships"] + } + assert ("contains", "sym::service.py::Service", "sym::service.py::Service.run") in relationships + assert ("calls", "sym::service.py::Service.run", "sym::service.py::helper") in relationships + + def test_duplicate_symbol_occurrences_are_preserved(tmp_path): root = repo(tmp_path) write( From 66e4da7df80196c161725404ce5182f025c7b13d Mon Sep 17 00:00:00 2001 From: nia-sg-bot Date: Thu, 27 Aug 2026 01:33:13 +0530 Subject: [PATCH 2/3] fix(structural): remove unreachable async node checks --- diffgraph/structural.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/diffgraph/structural.py b/diffgraph/structural.py index 31d05a5..96f0201 100644 --- a/diffgraph/structural.py +++ b/diffgraph/structural.py @@ -180,11 +180,7 @@ def identifiers(node) -> set: def visit(node, parents: Tuple[Tuple[str, str], ...] = ()) -> None: next_parents = parents - if node.type in ( - "class_definition", - "function_definition", - "async_function_definition", - ): + if node.type in ("class_definition", "function_definition"): name_node = _name_child(node) if name_node is not None: name = _node_text(content, name_node) @@ -271,11 +267,7 @@ def visit(node, parents: Tuple[Tuple[str, str], ...] = ()) -> None: caller = parents[-1][0] if parents else None ancestor = node.parent while ancestor is not None: - if ancestor.type in ( - "class_definition", - "function_definition", - "async_function_definition", - ): + if ancestor.type in ("class_definition", "function_definition"): body = ancestor.child_by_field_name("body") if body is not None and not ( body.start_byte <= node.start_byte From decb836dd8f1ca08517bc04e424c0dd3c601541c Mon Sep 17 00:00:00 2001 From: nia-sg-bot Date: Thu, 27 Aug 2026 15:33:05 +0530 Subject: [PATCH 3/3] test(structural): cover async function callers --- tests/test_structural.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_structural.py b/tests/test_structural.py index a3425aa..4f87062 100644 --- a/tests/test_structural.py +++ b/tests/test_structural.py @@ -727,6 +727,8 @@ def test_async_python_functions_are_structural_symbols_and_callers(tmp_path): root, "service.py", "async def helper():\n return 1\n\n" + "async def orchestrate():\n" + " return await helper()\n\n" "class Service:\n" " async def run(self):\n" " return await helper()\n", @@ -736,6 +738,8 @@ def test_async_python_functions_are_structural_symbols_and_callers(tmp_path): root, "service.py", "async def helper():\n return 2\n\n" + "async def orchestrate():\n" + " return await helper()\n\n" "class Service:\n" " async def run(self):\n" " return await helper()\n", @@ -747,6 +751,7 @@ def test_async_python_functions_are_structural_symbols_and_callers(tmp_path): symbols = {item["id"]: item for item in artifact["symbols"]} assert symbols["sym::service.py::helper"]["kind"] == "function" assert symbols["sym::service.py::helper"]["change_kind"] == "modified" + assert symbols["sym::service.py::orchestrate"]["kind"] == "function" assert symbols["sym::service.py::Service.run"]["kind"] == "method" assert symbols["sym::service.py::Service.run"]["parent_id"] == "sym::service.py::Service" relationships = { @@ -754,6 +759,7 @@ def test_async_python_functions_are_structural_symbols_and_callers(tmp_path): for item in artifact["relationships"] } assert ("contains", "sym::service.py::Service", "sym::service.py::Service.run") in relationships + assert ("calls", "sym::service.py::orchestrate", "sym::service.py::helper") in relationships assert ("calls", "sym::service.py::Service.run", "sym::service.py::helper") in relationships