Credence.Pattern.NoRepeatedDivRem (credence v0.7.0)

Copy Markdown

Detects a div/2 or rem/2 result that is already bound to a variable by an anchor statement and then re-computed verbatim later in the same function clause body, and rewrites the later occurrences to read the bound variable instead.

Bad

defp check(x) do
  a = rem(x, 2)
  b = rem(x, 2)
  {a, b}
end

Good

defp check(x) do
  a = rem(x, 2)
  b = a
  {a, b}
end

Why so narrow

Generic common-subexpression elimination for div/rem is not behaviour-preserving: it has to invent a variable name, find an insertion point that dominates every use, and prove the arguments are stable and side-effect-free. Getting any of those wrong changes the answer. This rule fires only on the one shape it can prove safe:

  • The first (source-earliest) occurrence of the repeated call is a top-level statement of the body block of the form lhs = div(a, b) / lhs = rem(a, b) — the anchor. The anchor's evaluation (and any ArithmeticError it would raise) therefore happens exactly where it did before; later occurrences are only reached, in the original, after the anchor already succeeded.

  • Every argument is a bare variable or a numeric literal — never a call — so the call has no side effects and re-running it fewer times is unobservable.

  • No argument variable, and lhs itself, is bound anywhere in the body (=, <-, or a ->/fn clause head). That rules out rebinding and shadowing, so the value bound by the anchor equals what each later occurrence would compute, and lhs is in scope (and unchanged) at every later occurrence — including inside closures, which capture it.

Under those conditions each later op(a, b) is replaced by lhs. Anything outside this shape (the first occurrence is a sub-expression, not a clean binding; a non-trivial argument; a rebound variable) is deliberately left untouched.