Credence.Pattern.NoCaseOnParamDispatch
(credence v0.7.0)
Copy Markdown
Detects a single-parameter function clause whose entire body is a case
that dispatches on that parameter, and rewrites it into multi-clause
function heads — the idiomatic Elixir way to express this.
Bad
def pick_coins(coins) do
case coins do
[] -> 0
[first] -> first
[first, second] -> max(first, second)
_ -> do_pick_coins(coins, 0, 0)
end
endGood
def pick_coins([]), do: 0
def pick_coins([first]), do: first
def pick_coins([first, second]), do: max(first, second)
def pick_coins(_ = coins), do: do_pick_coins(coins, 0, 0)Scope — what it flags
This rule is deliberately narrowed to a single-parameter core where the rewrite is provably behaviour-preserving for every input. It fires only when:
- The clause is a plain
def/defphead (nowhenguard on the head) with exactly one parameter that is a bare variablev. - The clause body is only a
do:body (norescue/catch/after) and is a singlecase v do … endwhose subject is exactly that parameter. - The
casehas at least 2 clauses, none using a^pin in its pattern. - At least one clause is an unguarded catch-all (a bare variable or
_), so thecaseis total over the parameter.
Why these limits (safety)
Each limit closes a way the rewrite could change the answer:
- Single bare-variable parameter / subject is that parameter. A bare
variable is side-effect-free, so there is no double-evaluation when its
pattern matching moves into the function head. Multi-parameter / tuple
dispatch (
case {x, y}) is not flagged — reconstructing reordered or partial parameter lists is a separate, riskier transformation. - Totality (an unguarded catch-all). A non-total
caseraisesCaseClauseError, but the equivalent function heads raiseFunctionClauseError— a different exception. We only fire when both constructs are total over the parameter, so neither ever raises. - No
^pin.case v do ^v -> … endmatches against the in-scope subject; as a function headdef f(^v)the pinned variable is unbound. - No
rescue/catch/after, body is exactly thecase. Any extra statement or clause would be silently dropped by the rewrite.
Guards on individual case clauses are preserved verbatim as head guards —
guard semantics (including error-swallowing) are identical in a case clause
and a function head, so this is safe. Sibling clauses of the same function
need no special handling: the flagged clause's bare-variable head is itself a
function-level catch-all, so later siblings are already unreachable and
earlier siblings shadow identically before and after the rewrite.
Reconstructing the head
Each case clause pattern -> body becomes def name(head) do body end,
where head keeps the parameter bound when the body or a clause guard still
refers to it: if pattern already binds the parameter name it is used as-is;
otherwise, when the body/guard mention the parameter, the head becomes
pattern = v so the original name stays in scope.