Credence.Pattern.RemoveUnreachableClausesAfterCatchall (credence v0.8.0)

Copy Markdown

Removes a redundant duplicate catch-all clause — a bare catch-all clause (all arguments bare variables / underscores, no guard) that follows an earlier catch-all clause for the same name and arity and is therefore unreachable at runtime.

The first catch-all already matches every call, so any later catch-all can never execute. This triggers the compiler warning "this clause cannot match because a previous clause at line N always matches", which blocks compilation under --warnings-as-errors. Because both clauses match the exact same domain (every input of that arity), the later one is provably dead code that no reordering could ever make reachable — so removing it preserves behaviour on every input.

Bad

defmodule Solution do
  def exactly_one_replace([], []), do: false
  def exactly_one_replace([h1 | t1], [h2 | t2]) when h1 == h2 do
    exactly_one_replace(t1, t2)
  end
  def exactly_one_replace(t1, t2) do
    t1 == t2
  end
  def exactly_one_replace(_, _), do: false  # unreachable duplicate catch-all!
end

Good

defmodule Solution do
  def exactly_one_replace([], []), do: false
  def exactly_one_replace([h1 | t1], [h2 | t2]) when h1 == h2 do
    exactly_one_replace(t1, t2)
  end
  def exactly_one_replace(t1, t2) do
    t1 == t2
  end
end

What it deliberately does NOT touch

A clause that is unreachable only because of its position — a guarded clause or a clause with a literal/structural pattern placed after a catch-all — is left alone. Such a clause matches a narrower domain than the catch-all, so it is dead only because the author ordered the clauses wrongly; removing it (or reordering, which changes dispatch) would silently change the program's behaviour or mask a real bug. Only a duplicate catch-all — which can never express any distinct behaviour — is safe to delete, so that is all this rule removes.

A clause with a dynamic head name (def unquote(op)(a, b), generated inside a macro) is also left alone: its name is only known at expansion time, so distinct functions would otherwise collapse into one {nil, arity} group and a live clause could be deleted as a phantom duplicate.

Auto-fix

Deletes each catch-all clause that follows the first catch-all in a consecutive group of def/defp clauses sharing the same name and arity, but only when every clause after the first catch-all is itself a bare catch-all. The first catch-all is preserved — it is the clause that handles all inputs.