mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 16:09:26 +00:00
`defdelegate name(args), to: Mod` declares an invocable function with the same `call` head shape as `def`, but was missing from the def-family recognizer, so no node was minted and same-module calls to the delegated name dangled. Add `defdelegate` to the def-family tuple and to the call-pass skip set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
"""`defdelegate` defines an invocable function in the module (#4132).
|
|
|
|
`defdelegate greet(name), to: Greeter` declares a callable `greet` whose body is
|
|
delegated to another module. It has the same `call` head shape as `def`, but was
|
|
missing from the def-family recognizer, so no node was minted and same-module
|
|
calls to the delegated name dangled.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from graphify.extract import extract
|
|
|
|
|
|
def _graph(tmp_path: Path):
|
|
src = tmp_path / "facade.ex"
|
|
src.write_text(
|
|
"defmodule Facade do\n"
|
|
" defdelegate start(x), to: Worker\n"
|
|
" defdelegate stop(x), to: Worker, as: :halt\n"
|
|
" def run(x) do\n"
|
|
" start(x)\n"
|
|
" stop(x)\n"
|
|
" end\n"
|
|
"end\n",
|
|
encoding="utf-8",
|
|
)
|
|
result = extract([src], cache_root=tmp_path / "graphify-out")
|
|
label = {n["id"]: n["label"] for n in result["nodes"]}
|
|
return result, label
|
|
|
|
|
|
def _edges(result, label, relation):
|
|
return {
|
|
(label[e["source"]], label[e["target"]])
|
|
for e in result["edges"]
|
|
if e["relation"] == relation and e["source"] in label and e["target"] in label
|
|
}
|
|
|
|
|
|
def test_defdelegate_defines_functions(tmp_path: Path) -> None:
|
|
result, label = _graph(tmp_path)
|
|
labels = set(label.values())
|
|
assert "start()" in labels, "defdelegate function dropped"
|
|
assert "stop()" in labels, "defdelegate-with-as function dropped"
|
|
methods = _edges(result, label, "method")
|
|
assert ("Facade", "start()") in methods
|
|
assert ("Facade", "stop()") in methods
|
|
|
|
|
|
def test_defdelegate_same_module_calls_resolve(tmp_path: Path) -> None:
|
|
result, label = _graph(tmp_path)
|
|
calls = _edges(result, label, "calls")
|
|
assert ("run()", "start()") in calls, "call to a delegated function dangled"
|
|
assert ("run()", "stop()") in calls
|
|
|
|
|
|
def test_defdelegate_leaves_no_dangling_edges(tmp_path: Path) -> None:
|
|
result, _ = _graph(tmp_path)
|
|
node_ids = {n["id"] for n in result["nodes"]}
|
|
dangling = [
|
|
e for e in result["edges"]
|
|
if e["source"] not in node_ids or e["target"] not in node_ids
|
|
]
|
|
assert not dangling, f"defdelegate produced dangling edges: {dangling}"
|