Skip to content

Elixir: four extraction defects outside the #1239 D1-D8 list (two recorded as working today) #1729

Description

@henry-hz

Summary

While auditing Elixir extraction against a real 829-file Phoenix application, I found four defects that are not in the D1–D8 list of #1239 / docs/elixir-lsp/PLAN.md. Two of them contradict things that plan records as working today, so I am raising them here before they cost @holsee debugging time.

I also have a fork with a broader Elixir grammar-layer rewrite. I am not proposing that upstream — see "Overlap" at the bottom. This issue is about the four defects and about making my other work available if it is useful.

The four defects

1. first_string_arg is never populated for any Elixir call

tree-sitter-elixir attaches no field name to a call's arguments node — its whole field set is key, left, operand, operator, quoted_start, quoted_end, right, target, value. handle_calls() reads ts_node_child_by_field_name(node, "arguments"), which is therefore always null for Elixir.

Consequence: route paths, HTTP/async service URLs and config keys are all dead for Elixir, silently. extract_defs.c has always carried a positional fallback (elixir_call_args()) for exactly this reason; the call side never got one.

→ PR #1721 (open, 54 lines, additions only).

2. Phoenix channel extraction is unreachable in both branches

PLAN.md §1.2 lists "Phoenix channels/PubSub (works today)extract_channels.c:875-970". It does not work — it has never emitted a single Channel node:

  • the emit path (elixir_process_call) reads the same nonexistent arguments field, so elixir_emit_second_arg() returns early every time
  • the listener path is dispatched by strcmp(kind, "def") == 0, and there is no def node type in the grammar — Elixir definitions are call nodes. elixir_process_function_def() is dead code, and it also reads name and parameters fields the grammar does not define.

Fixed and verified against Phoenix.PubSub.broadcast/subscribe, bare push/broadcast, and two handle_in clauses including a guarded one.

3. Phoenix routers mint zero Route nodes

PLAN.md §1.2 lists "Phoenix/Oban/Broadway service patterns (work today)". Oban and Broadway do. Phoenix routes do not.

Every entry in route_reg_suffixes is prefixed . or :: (.get, ::get, .MapGet) because it is matched against the tail of a qualified callee. Phoenix registers with bare macros:

get "/wallets", WalletController, :index

so the callee is exactly get and cbm_service_pattern_route_method() returns NULL. The resolved-QN route-library table cannot help either — Phoenix.Router lives in deps/ and never resolves in-tree.

Fix adds an exact-match bare table (get, post, put, patch, delete, head, options, live, resources, forward), gated to Elixir and consulted before resolution. Exact and not suffix on purpose: a suffix rule would classify widget, target and budget as GET routes. extract_handler_arg() also needed to accept Elixir's alias/dot handler node, without which no route gets a HANDLES edge.

Depends on #1721 for the path argument.

4. Elixir functions carry no complexity, fingerprint or line count

Every Elixir Function ships complexity = 0, cognitive = 0, lines = 0 and no MinHash fingerprint, so Elixir is invisible to complexity/bottleneck queries and never participates in SIMILAR_TO — while the query_graph tool description states that "every Function and Method node carries queryable complexity properties".

cbm_compute_complexity() matches on node type, and Elixir has no branching node types: if, unless, case, cond, with, for, try and receive are ordinary call nodes distinguished by the identifier in callee position, and every arm is a stab_clause. That is also why elixir_branch_types = {"call"} in lang_specs.c has never been usable — it would score every function call as a decision.

Measured effect

Fresh index of an 829-file Phoenix application, plus elixir-lang/elixir (6,346 .ex functions) for the complexity numbers:

before after
Route nodes 0 180
HANDLES edges 0 153
Functions with complexity > 0 — elixir-lang/elixir 4,002 (63%) 4,753 (75%)
Functions with complexity > 0 — Phoenix app 2,023 (49%) 2,500 (61%)
Functions with a fingerprint 7 / 4,504 most

For scale, the TypeScript and Python sources in that same Phoenix repo score 20 of 386 (5%).

Overlap with #1239 — and an offer

My fork also carries a grammar-layer rewrite that overlaps @holsee's D1–D8 almost item for item: guarded def heads (D1), enclosing-function attribution (D2), name/arity identity (D3 — resolved as an arities def field, keeping the QN string format unchanged, which is the option PLAN.md flags as preferable), the full def* family (D4), imports nested in defmodule (D5), nested module QNs (D6), and stronger Elixir test contracts (D8 — 18 extraction tests).

I am deliberately not opening PRs for any of that. @holsee has been staging this since July across 15+ branches and it would be a competing implementation, which helps nobody.

If it is useful as reference material — a second implementation to diff against, or tests to lift — it is at henry-hz/codebase-memory-mcp, branch fix/elixir-routes-channels-complexity-heex. Take anything, or ignore it entirely; no attribution needed. One caveat if you do look: the dotted-module import resolution in pass_pkgmap.c there has no test and touches a candidate-ordering path shared with Lua and R, so treat that part as unproven.

Proposed delivery

Four small PRs, each independent except where noted, all signed off and gated:

  1. fix(elixir): populate first_string_arg for Elixir calls #1721first_string_arg (open)
  2. channels — independent
  3. complexity + fingerprint + lines — independent
  4. Phoenix routes — after fix(elixir): populate first_string_arg for Elixir calls #1721

Happy to reorder, split further, fold any of them into @holsee's phases instead, or drop them if they conflict with the staged plan. Also happy to hold 2–4 until there is appetite.

Separately: .heex / .eex templates are not indexed at all today, so the LiveView template layer is invisible. That needs a vendored grammar, which CONTRIBUTING says requires prior approval — I have it working locally and will open a separate issue rather than assume.

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions