Credence.Pattern.NoRepeatedDivRem
(credence v0.7.1)
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}
endGood
defp check(x) do
a = rem(x, 2)
b = a
{a, b}
endWhy 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 anyArithmeticErrorit 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
lhsitself, is bound anywhere in the body (=,<-, or a->/fnclause head). That rules out rebinding and shadowing, so the value bound by the anchor equals what each later occurrence would compute, andlhsis 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.