Skip to content

fix: eliminate dead-code false positives from optimistic effect classification - #37

Open
gilbertwong96 wants to merge 5 commits into
elixir-vibe:masterfrom
gilbertwong96:fix/pubsub-broadcast-effect
Open

fix: eliminate dead-code false positives from optimistic effect classification#37
gilbertwong96 wants to merge 5 commits into
elixir-vibe:masterfrom
gilbertwong96:fix/pubsub-broadcast-effect

Conversation

@gilbertwong96

@gilbertwong96 gilbertwong96 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Problem

mix reach.check --dead-code --smells reported 26 false positives. They share one root cause: the effect classifier optimistically marks side-effecting calls as :pure — from beam inference, from @spec/ExCk return types, or from module-level classification — so fire-and-forget calls (whose :ok results are intentionally discarded) get flagged as dead code.

Fixes

  1. Phoenix.PubSub.broadcast / Task.Supervisor.start_child:send (0fbd5d0)
    Beam-based dependency inference can't see the message/process side effects; classify them in the built-in messaging table instead.

  2. HEEx events/components inside conditionals connect to template output (9ee97e0)
    :if/:for desugar to :case/:comprehension nodes whose branch bodies were never wired to the block output, so phx-click events and component calls inside conditionals (including EEx <%= if %> branches) looked unused.

  3. {:ok, _} result-wrapper return types are not :pure (e2bd412)
    @spec/ExCk inference treated {:ok, _} | {:error, _} as plain data — but that is the canonical signature of side-effecting operations (Repo, GenServer, HTTP, File). Conservative: unprovable functions are no longer reported dead.

  4. Phoenix.Controller.delete_csrf_token is not :pure (ae00b18)
    The Phoenix plugin classified the whole Phoenix.Controller module as pure; the token-drop call mutates process state (standard phx.gen.auth login flow).

  5. __live_event__ excluded from the redundant-computation smell (b628ced)
    Two buttons sharing an event name but differing in phx-value-* were flagged as duplicates — event registrations are compile-time template artifacts with no runtime cost.

Verification

  • Full suite: 1257 passed (each fix ships a regression test)
  • The 26 dead-code/smell false positives reported above are eliminated

Dependency beam inference classifies Phoenix.PubSub.broadcast/3 as
:pure, so callers that fire-and-forget the :ok result (plus wrapper
functions and __aliases__ arguments) show up as dead code. Same for
Task.Supervisor.start_child/2.

Add both to the built-in messaging classification so classification
doesn't depend on beam inference.
@gilbertwong96
gilbertwong96 force-pushed the fix/pubsub-broadcast-effect branch from 5d703a0 to 0fbd5d0 Compare August 1, 2026 19:30
:if/:unless/:cond desugar to :case nodes and :for to :comprehension,
so their branch bodies are not direct block children. connect_heex_parts
only wired direct children to the block output, leaving events and
component calls inside conditionals (including EEx <%= if %> branches,
whose bodies are plain blocks without a HEEx origin) with no output
edges — dead-code false positives.

Recursively connect branch bodies: every child inside a branch is wired
to the branch output, and the branch output to the enclosing block
output. Nested control flow is handled recursively.
@gilbertwong96
gilbertwong96 force-pushed the fix/pubsub-broadcast-effect branch from 2fc8515 to 4c69a6b Compare August 2, 2026 05:32
classify_from_spec / classify_from_inferred marked functions whose
@SPEC or ExCk-inferred signature returns {:ok, _} | {:error, _} as
:pure, because result tuples look like plain data. But that signature
is the canonical shape of side-effecting operations (Repo, GenServer,
HTTP, File) — fire-and-forget calls like record_read/2 were
reported as dead code.

Treat any return type whose leading element is an :ok/:error atom as
non-pure in both the typespec and ExCk inference paths. This is a
conservative fix: functions that can't be proven pure are no longer
reported as dead, which is the right trade-off for an advisory check.

Alternative approaches were evaluated and rejected:
- Project-level effect inference prewarm: the ExCk heuristic still
  contaminates it — inference order determines whether a function or its
  callers get classified first, and the optimistic :pure wins either way.
- Signature heuristics alone for unions were insufficient: ExCk encodes
  {:ok, nil} as a tuple, not a union.
@gilbertwong96
gilbertwong96 force-pushed the fix/pubsub-broadcast-effect branch from 4c69a6b to e2bd412 Compare August 2, 2026 06:22
The Phoenix plugin treats every call in Phoenix.Controller / Plug.Conn
as :pure via @pure_remote_modules. delete_csrf_token/0 (delegated to
Plug.CSRFProtection) mutates process state to drop the CSRF token —
the standard fire-and-forget call in phx.gen.auth login flows — so its
discarded :ok result was reported as dead code.

Exclude it explicitly before the module-level pure classification.
Two buttons with the same phx-click event name but different
phx-value-* attributes (e.g. clear-filter vs set-filter in a tag
strip) were flagged as duplicate calls, because __live_event__ nodes
only carry the event name string — the distinguishing values live in
separate dynamic-attr nodes. Event registrations are compile-time
template artifacts with no runtime cost, so duplicate registration is
never a real redundancy. Exclude __live_event__ alongside the other
compiler-generated nodes (:__aliases__) already in @excluded_fns.
@gilbertwong96 gilbertwong96 changed the title fix: classify PubSub broadcast and Task.Supervisor.start_child as :send fix: eliminate dead-code false positives from optimistic effect classification Aug 2, 2026
@gilbertwong96
gilbertwong96 force-pushed the fix/pubsub-broadcast-effect branch from b628ced to 6dce258 Compare August 2, 2026 07:25
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.

1 participant