Skip to content

Elixir extractor: unqualified bare-name calls attach to unrelated same-named nodes, producing fake god nodes #4001

Description

@igmarin

Upstream issue draft for Graphify-Labs/graphify

Title: Elixir extractor: unqualified bare-name calls attach to unrelated same-named nodes, producing fake god nodes

Summary

The Elixir AST extractor resolves unqualified function calls by bare name, ignoring import scope and arity. When a name is defined somewhere in the repo (so a definition-anchored node exists) and called elsewhere as a DSL/framework function, every call site attaches to the definition node. One node can collect hundreds of false calls edges, become the #1 god node, and distort Louvain clustering.

Observed (repo: Elixir umbrella, ~10 apps, migrations under priv/repo/migrations/; graphifyy 0.9.56)

  • table() — node anchored at apps/ecosystem_web/lib/ecosystem_web/components/core_components.ex:677 (a Phoenix function component). Collected 690 calls edges whose source_file is a migration file (change()/up()/down() callers across 8 apps) — these are Ecto.Migration table/2 usages. Result: 691-edge v3: semantic query with embeddings #1 god node, clustered with migrations instead of UI components.
  • remove() — anchored at an ETS registry helper (agent_session_registry.ex:91); 106 edges from migrations (Ecto.Migration remove/1).
  • execute() — two separate definition-anchored nodes (a mix task and a seeds file) each collected migration execute/1 edges (54 + 38).
  • create() ×4, add(), index() — same pattern (Ecto.Migration DSL, anchored in unrelated manager/notification/config files).
  • select() — anchored at a domain function (membership.ex:12); 147 edges from other apps' Ecto.Query pipelines.
  • build() — anchored at a test factory; 109 edges from other apps' factory usages (ExMachina build/2).
  • push_flash() — anchored at one LiveView; 89 edges from other components (Phoenix.LiveView push_flash/3).

Root cause

Call-edge resolution does not consider the caller's imports/aliases. The only node that exists for a bare name is wherever the name happens to be defined; unqualified DSL calls therefore join it.

Suggested fix (either)

  1. Resolve unqualified calls against the caller module's import/alias/require set before matching a node, or
  2. When the call-site file differs from the def-site file and the bare name matches a known DSL surface (Ecto.Migration, Ecto.Query, Kernel, ExMachina, Phoenix.LiveView), emit a qualified external-dependency node (e.g. ecto_migration_table, label "Ecto.Migration table/2") instead of joining the definition node.

Workaround we used (happy to contribute)

Detection: any node whose source_file is not a migration context but whose incident edges are >50% from migration paths (degree ≥ 5), plus known framework names with >20 cross-file calls edges. Repair: re-point those edges to external DSL nodes (confidence: INFERRED, 0.95), keep the definition node's real local edges, re-cluster. 1,212 edges re-pointed across 13 nodes; god-node list went from 4/10 artifacts to a clean ranking. Script available on request.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions