Credence.Pattern.NoMapKeysForMembership (credence v0.7.1)

Copy Markdown

Detects x in Map.keys(m) and x not in Map.keys(m) used for membership testing, and rewrites to Map.has_key?(m, x) / not Map.has_key?(m, x).

Map.keys/1 builds an O(n) list just to check membership — Map.has_key?/2 does the same check in O(log n) without allocating a list.

Bad

Enum.filter(queue, &(&1 not in Map.keys(visited)))
if x in Map.keys(cache), do: cached, else: compute(x)

Good

Enum.reject(queue, &Map.has_key?(visited, &1))
if Map.has_key?(cache, x), do: cached, else: compute(x)

Safety: only side-effect-free left operands

x in Map.keys(m) evaluates the right operand (Map.keys(m)) before the left (x), whereas the rewrite Map.has_key?(m, x) evaluates x before m. When the left operand has observable side effects this reorders them, so the rule fires only when the left operand is statically side-effect-free — a bare variable or a capture argument (&1). For those, reordering is unobservable and the rewrite is behaviour-preserving (membership via in/Enum.member? and map-key lookup both use term equality, and both raise BadMapError on a non-map).