Repository navigation
fix(elixir): emit calls edges for keyword-form def bodies - #4210
rtmalikian wants to merge 1 commit into
Conversation
One-line definitions (`def a(x), do: b(x)`) have no do_block node: the body is the value of the `do:` pair in the arguments keywords node. The extractor only registered do_block bodies for the call-walk pass, so every call inside a keyword-form body was silently dropped from the graph. When there is no do_block, take the `do:` pair value as the body and walk it the same way (Fixes Graphify-Labs#4207). AI assistance: implementation drafted with Muse (Anthropic), reviewed and verified by the author. Signed-off-by: Raphael Malikian <rtmalikian@gmail.com>
|
Thanks for the pull request, @rtmalikian. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. PR-changed functions: 1/1 verified (0 proven, 1 may-equivalent, 0 distinguished) · 0 not verified.
Graphify review — findings
Fixes dropped calls edges for keyword-form Elixir definitions (def a(x), do: b(x)) by having _get_do_keyword_body pull the do: value out of the arguments keywords and queue it for the call walk whenever a definition has no do_block. This also covers when-guarded heads. Definitions with neither form register no body, same as before. New tests check that keyword bodies produce edges to local functions and no edges to undefined ones.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 37 functions depend on the 25 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract_elixir()— 14 callers, 6 callees - new:
walk()— 1 callers, 7 callees
Verification — 37 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 37 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
2 of 345 test file(s) selected (1%) via static blast radius.
tests/test_elixir_keyword_def_calls.py— impact, changed-testtests/test_languages.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
No difference found (not proven): No behavior difference found in extract\_elixir (not a proof).
The verifier ran both versions of extract\_elixir on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| return sub | ||
| return None | ||
|
|
||
| def walk(node, parent_module_nid: str | None = None) -> None: |
There was a problem hiding this comment.
walk()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
Landed in v0.9.81 via an authorship-preserving cherry-pick, so your commit is on |
|
Thanks for landing it, @safishamsi — glad the fix made it into v0.9.81. Happy to help again. |
What does this PR do?
Fixes #4207.
One-line Elixir definitions (
def a(x), do: b(x)) have nodo_blocknode: tree-sitter-elixir puts the keyword body in theargumentsnode's trailingkeywordschild, as the value of thepairwhose keyword isdo:. The extractor only registereddo_blockbodies infunction_bodiesfor the call-walk pass, so every call inside a keyword-form body was silently dropped from the graph — a real codebase loses a large share of its local call edges.The fix: when a
def/defp/defmacro/defmacrop/defguard/defguardphas nodo_block, take the value of thedo:pair in itsargumentsas the body and walk it the same way (new_get_do_keyword_bodyhelper, mirroring the existing_get_defimpl_targetpair-walking style).Changes
graphify/extractors/elixir.py_get_do_keyword_body()helper; in thedef-family branch, fall back to thedo:keyword-pair value whendo_block_nodeis absent (+32 lines)tests/test_elixir_keyword_def_calls.pywhen-guarded keyword head, no dangling edges for undefined callees, block-form unchanged (+70 lines)Type of change
Verification & Invariants
Invariant protected: every defined function whose body the extractor recognizes gets its calls walked exactly once, so
callsedges reflect the source. Limitation: only thedo:keyword is handled;else:/rescue:keyword bodies are not walked, matching the block form which only walksdo_blockchildren.How was this tested?
Environment: Python 3.11, tree-sitter 0.25 + tree-sitter-elixir 0.3 (repo-pinned ranges), offline — no LLM/provider calls.
Graphify-specific checklist
uv run python -m tools.skillgen --bless) when changing their source fragments. — N/A: no skill fragments touched (tools/skillgenhas no reference to the Elixir extractor).Additional Notes
Author: Raphael Malikian — rtmalikian@gmail.com. If you'd like similar help with your own project or codebase, I'm open to collaborate — feel free to reach out.
AI assistance disclosure: implementation drafted with Muse (Anthropic), reviewed and verified by the author.