Normalizes Elixir AST for structural comparison.
Transforms an AST so that structurally equivalent code produces identical output regardless of variable names, metadata, or (optionally) literal values.
All normalizations are performed in at most two AST traversals:
Pass 1 (always, single fused walk):
- Metadata stripping — removes line numbers, columns, counters, and other compiler metadata from every node.
- Boolean operator canonicalization —
&&/||/!are rewritten toand/or/notso stylistic choice between short-circuit and keyword operators doesn't affect comparison. - Sigil expansion —
~w(foo bar)ais expanded to[:foo, :bar](and likewise for string modifiers) so sigil word-lists match their literal equivalents. - Pipe normalization (optional) —
x |> f()is rewritten tof(x). - Variable normalization — replaces variable names with positional
placeholders (
:$0,:$1, …) based on first-occurrence order.
Pass 2 (abstract mode only, single recursive walk):
- Literal abstraction — replaces concrete literals with type-tagged placeholders to detect Type-II clones.
- Map/struct field sorting — sorts key-value pairs so that
%{b: 1, a: 2}and%{a: 2, b: 1}produce the same hash. - Guard abstraction — in
whenclauses, replaces all function/macro call names with a:__guard__placeholder so thatwhen is_binary(x)andwhen is_atom(x)produce the same hash. Covers all Kernel guards, Erlang BIF guards,defguardmacros, and library guards likeInteger.is_even/1.
Summary
Functions
Normalize an AST fragment.
Replace all variable names with positional placeholders based on binding order.
Remove all metadata from every AST node, keeping only structural shape.
Types
@type option() :: {:literal_mode, :keep | :abstract} | {:normalize_pipes, boolean()}
Functions
Normalize an AST fragment.
Options
:literal_mode—:keeppreserves literal values (Type-I detection),:abstractreplaces them with placeholders (Type-II detection). Defaults to:keep.:normalize_pipes— whentrue, convert pipe chains to nested calls sox |> f()matchesf(x). Defaults tofalse.
Replace all variable names with positional placeholders based on binding order.
foo + bar and x + y both become :"$0" + :"$1".
Remove all metadata from every AST node, keeping only structural shape.