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!
endGood
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
endWhat 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.