fix(zig): extract methods declared on enums and unions

Zig lets enums and unions carry methods like structs, but only the struct branch recursed
into the container body, so enum/union methods and the calls in their bodies were dropped.
Walk enum/union children the same way, parenting methods to the container with the same id
scheme; variants and fields are not turned into nodes.
This commit is contained in:
rajatnagda45
2026-08-24 13:15:23 +01:00
committed by safishamsi
parent 512f9863bd
commit 6930e4fa90
2 changed files with 65 additions and 0 deletions
+6
View File
@@ -124,6 +124,12 @@ def extract_zig(path: Path) -> dict:
type_nid = _make_id(stem, type_name)
add_node(type_nid, type_name, line)
add_edge(file_nid, type_nid, "contains", line)
# Zig enums and tagged unions can declare methods just like
# structs (`pub fn ...` inside the container). Recurse so those
# methods — and the calls made from their bodies — are captured
# rather than dropped along with the whole method layer.
for child in value_node.children:
walk(child, parent_struct_nid=type_nid)
return
if value_node and value_node.type in ("builtin_function", "field_expression"):
+59
View File
@@ -3788,3 +3788,62 @@ def test_markdown_heading_id_is_stable_regardless_of_frontmatter():
stem = _file_stem(Path(src_file))
assert _make_id(stem, "Overview") in heading_ids
assert _make_id(stem, "Details") in heading_ids
# ── Zig ───────────────────────────────────────────────────────────────────────
from graphify.extract import extract_zig
_needs_zig = pytest.mark.skipif(
_ilu.find_spec("tree_sitter_zig") is None,
reason="tree-sitter-zig not installed",
)
@_needs_zig
def test_zig_enum_and_union_methods_are_extracted(tmp_path):
"""Methods declared inside a Zig enum or tagged union must be captured.
Only `struct` containers recursed into their members, so `pub fn` methods on
an `enum`/`union` — and every call made from those method bodies — were
dropped along with the whole method layer of the type.
"""
src = (
"const Color = enum {\n"
" red,\n"
" green,\n"
" pub fn isRed(self: Color) bool {\n"
" return self == .red;\n"
" }\n"
"};\n"
"\n"
"const Shape = union(enum) {\n"
" circle: f32,\n"
" pub fn area(self: Shape) f32 {\n"
" return helper();\n"
" }\n"
"};\n"
"\n"
"fn helper() f32 {\n"
" return 1.0;\n"
"}\n"
)
f = tmp_path / "shapes.zig"
f.write_text(src)
r = extract_zig(f)
assert "error" not in r
method_targets = {
e["target"] for e in r["edges"] if e["relation"] == "method"
}
id_to_label = {n["id"]: n["label"] for n in r["nodes"]}
method_labels = {id_to_label[t] for t in method_targets}
assert ".isRed()" in method_labels, "enum method dropped"
assert ".area()" in method_labels, "union method dropped"
# A call made from an enum/union method body must resolve too.
calls = {
(id_to_label.get(e["source"], e["source"]),
id_to_label.get(e["target"], e["target"]))
for e in r["edges"] if e["relation"] == "calls"
}
assert (".area()", "helper()") in calls, "call from union method body dropped"