From fac14916652cda1cb302d95f66fb3a9afadb2ebc Mon Sep 17 00:00:00 2001 From: ayarodionov Date: Sun, 11 Oct 2026 16:59:44 -0400 Subject: [PATCH] fix(julia): name parametric functors by their type, not the variable `(b::Box{T})(x)` puts the type in a `parametrized_type_expression`, so taking the last identifier in the typed expression picked the variable `b` and emitted a bogus `b#call` node, split from `Box#call`. Read the last operand through `_type_name_of`, which already unwraps parametric heads. Co-Authored-By: Claude Opus 5.5 --- graphify/extractors/julia.py | 10 +++++++--- tests/test_languages.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/graphify/extractors/julia.py b/graphify/extractors/julia.py index a77864ca5..6d678af6c 100644 --- a/graphify/extractors/julia.py +++ b/graphify/extractors/julia.py @@ -194,9 +194,13 @@ def extract_julia(path: Path) -> dict: typed = next((c for c in callee.children if c.type in ("typed_expression", "unary_typed_expression")), None) if typed is not None: - ids = [c for c in typed.children if c.type == "identifier"] - if ids: - tname = _read_text(ids[-1], source) + # The type is the last operand; it may be parametric (`(b::Box{T})`), + # so read it through `_type_name_of` rather than the last identifier, + # which would be the variable `b`. + operands = [c for c in typed.children + if c.type in ("identifier", "parametrized_type_expression")] + tname = _type_name_of(operands[-1]) if operands else None + if tname: return tname + "#call", f"({tname})" return None diff --git a/tests/test_languages.py b/tests/test_languages.py index 4e9bdb2cb..8b01b127f 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -5728,3 +5728,17 @@ def test_julia_broadcast_and_pipe_calls(tmp_path): assert ("bcast()", "helper()") in calls assert ("param()", "helper()") in calls assert ("rettype()", "param()") in calls + + +def test_julia_parametric_functor_uses_type_not_variable(tmp_path): + f = tmp_path / "functor.jl" + f.write_text( + "struct Box{T}\n v::T\nend\n" + "(b::Box{T})(x) where T = x\n" + "(b::Box)(x, y) = x\n" + "(::Box{T})(x, y, z) where T = x\n" + ) + r = extract_julia(f) + call_nodes = [n for n in r["nodes"] if n["id"].endswith("_call")] + # All three methods of the Box functor share one node labelled by the type. + assert [n["label"] for n in call_nodes] == ["(Box)()"]