Skip to content

fix(elixir): extract functions whose only clause has a when guard (#3111) - #3118

Open
santhiprakash wants to merge 1 commit into
Graphify-Labs:v8from
santhiprakash:fix/elixir-guarded-single-clause-3111
Open

fix(elixir): extract functions whose only clause has a when guard (#3111)#3118
santhiprakash wants to merge 1 commit into
Graphify-Labs:v8from
santhiprakash:fix/elixir-guarded-single-clause-3111

Conversation

@santhiprakash

Copy link
Copy Markdown
Contributor

Summary

The Elixir AST extractor silently drops every function whose only clause carries a when guard. On a real 2,900-file Elixir/Phoenix codebase this hid 19 of 582 functions (every function of that shape) from the graph. No error is raised; the node is never created.

Problem

tree-sitter-elixir wraps a guarded head in a binary_operator:

  • def plain(x) do ...arguments children = ['call']
  • def guarded(x) when is_integer(x) do ...arguments children = ['binary_operator']

The extractor only looked for a direct call/identifier child of arguments, so func_name stayed None and the function was dropped. Multi-clause functions survived via an unguarded clause, which is why this was easy to miss.

Fix

Unwrap binary_operator (the when guard) to the left-hand head before reading the function name. Scoped to def/defp name extraction; the rest of the walker is unchanged.

This is independent of open #2970 (single-line def f(x), do: ... call-graph bodies). That PR does not unwrap guarded heads.

Verification

uv run --frozen pytest tests/test_languages.py -k elixir -q

9 passed, including test_elixir_guarded_single_clause_is_extracted covering:

case shape extracted
A single clause, no guard still extracted
B single clause, when guard now extracted
C multi-clause, one guarded still extracted
D defp, single clause, when guard now extracted

Reproduction (before fix)

defmodule Demo do
  def guarded(x) when is_integer(x) do
    x + 1
  end
end

extract_elixir produced Demo and demo.ex only — no guarded node.

Fixes #3111

- Problem: tree-sitter-elixir wraps `def f(x) when guard` in a binary_operator, so a function whose only clause is guarded was dropped from the graph.
- Fix: unwrap binary_operator heads before reading the function name so single-clause guarded `def`/`defp` create nodes like unguarded clauses.
- Verification: `uv run --frozen pytest tests/test_languages.py -k elixir -q` — 9 passed.

Fixes Graphify-Labs#3111

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. No changes could be formally verified in this run.


Graphify review — findings

Fixes the Elixir extractor dropping single-clause functions whose only head carries a when guard: extract_elixir now unwraps the binary_operator node tree-sitter wraps a guarded head in, walking down to the underlying call/identifier so guarded def/defp get nodes like any other. Adds test_elixir_guarded_single_clause_is_extracted covering plain, guarded, mixed multi-clause, and guarded-private cases.

No blocking issues surfaced.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 536 functions depend on the 536 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_elixir() — 11 callers, 5 callees

Verification — 536 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: 536 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify extract\_elixir.

The verifier did not have enough to check extract\_elixir, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 more finding(s) on lines outside this diff (see the check run).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Elixir extractor drops functions whose only clause has a when guard

1 participant