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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions