Credence.Pattern.NoManualListLast
(credence v0.7.1)
Copy Markdown
Detects hand-rolled reimplementations of List.last/1.
Why this matters
When NoListLast flags List.last/1, LLMs "fix" it by writing the
exact same O(n) traversal under a different name:
# Flagged — this IS List.last, just hand-rolled
defp get_last_element([val]), do: val
defp get_last_element([_ | rest]), do: get_last_element(rest)This has the same performance characteristics as List.last/1 but
adds unnecessary code. The real fix is to restructure the algorithm
to avoid needing the last element:
- Track the value in an accumulator during a reduce
- Reverse the list and take the head
- Destructure from the other end
Detection scope
A two-clause defp (or def) function with arity 1 where:
- One clause matches
[val](single-element list) and returnsval The other clause matches
[_ | rest]and recurses withrest
Auto-fix
Replaces the hand-rolled function with hd(Enum.reverse(list)) and rewrites
call sites within the same source file.
We deliberately avoid List.last/1: the hand-rolled form has no [] clause, so
it raises on the empty list, whereas List.last([]) returns nil — a behaviour
change. hd(Enum.reverse([])) raises (ArgumentError) like the original, so the
fix is behaviour-preserving (the only difference is the raised error's type on the
degenerate empty-list input).