# `Mutare.AST`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.2.1/lib/mutare/ast.ex#L1)

Small constructors and predicates for Sourceror AST nodes.

These helpers are the supported way for custom mutators to build AST. `literal/1` is the most important one: it builds literal nodes with fresh metadata, so Sourceror renders the new value rather than stale source text. It also handles string delimiters, numeric token metadata, and negative-number shape correctly.

`literal_value/1` reads supported literal nodes back to their values. The `sentinel_*` helpers return the same survivor markers used by the built-in families, and `numeric_alternatives/3` the same off-by-one/zero alternatives (with their variant labels and collapse rule) the numeric literal families produce. `absolute_alias/1`, `absolute_call/3`, and `remote_call/3` build references that are not affected by aliases or imports in the target source. `keyword_key/1` and `clean_var/1` cover the remaining node shapes a mutator emits into existing source: fresh keyword keys and re-declared bindings.

`parse!/1` and `to_string/1` expose the Sourceror round trip without requiring custom mutators to depend on Sourceror directly.

# `absolute_alias`

```elixir
@spec absolute_alias([atom()]) :: Macro.t()
```

Builds an absolute-qualified module alias.

The result is the `Elixir.`-prefixed form that alias and import resolution do
not rewrite. Use it when a mutation must refer to a specific module regardless of
aliases in the target source. Pair it with `absolute_call/3` to build a call.

    iex> Mutare.AST.absolute_alias([:Kernel])
    {:__aliases__, [], [:"Elixir", :Kernel]}

# `absolute_call`

```elixir
@spec absolute_call([atom()], atom(), [Macro.t()]) :: Macro.t()
```

Builds an absolute-qualified remote call.

The callee cannot be redirected by aliases or imports in the target source. This
is useful when a mutator replaces a call with a function from another module, such
as replacing `String.length(s)` with `Kernel.byte_size(s)`.

    iex> Mutare.AST.absolute_call([:Kernel], :==, [1, 2])
    {{:., [], [{:__aliases__, [], [:"Elixir", :Kernel]}, :==]}, [], [1, 2]}

# `clean_var`

```elixir
@spec clean_var(Macro.t()) :: Macro.t()
```

Strips a variable node's metadata, keeping its name and hygiene context.

Use it to re-declare a binding inside synthesized scaffolding — a wrapper call a
mutator builds around existing code — where the source line/column and token
metadata are stale but the context atom must survive for the variable to stay
the same variable.

    iex> Mutare.AST.clean_var({:user, [line: 3, column: 7], nil})
    {:user, [], nil}

# `empty_collection_literal?`

```elixir
@spec empty_collection_literal?(Macro.t()) :: boolean()
```

Whether `node` is an *always-empty enumerable literal* admitted on the right of
`in` in a guard: `List` → `[]`, `WordListLiteral` → `~w()`, or
`CharlistLiteral` → `~c""`.

In a guard, `x in <empty>` is equivalent to the `false` mutant that
`Mutare.Mutators.Conditional` already produces on the `in` node: guards have no
observable side effects and a guard error is a failed guard. `Mutare.Transform.Tag`
therefore drops the empty-literal sibling there. Body expressions do not use this
predicate because evaluating `x` can be observable.

# `erlang_call`

```elixir
@spec erlang_call(atom(), [Macro.t()]) :: Macro.t()
```

Builds a call to an `:erlang` BIF or operator.

The one form no `import` can redirect: an atom module reaches `:erlang` directly,
where an `Elixir.Kernel.` qualification still routes through Elixir's rewriter.
Mutare uses it for the operators it *generates* into the metamutant — the
activation gate, exclusion guards, and the coverage record — so a target that
narrows or replaces `Kernel`'s imports cannot change what they mean. In a guard,
Elixir compiles an `:erlang.andalso` or `:erlang.orelse` call to the short-circuit
operator itself — the form `Kernel.and/2` and `or/2` expand to there. Only in a
guard: in a body both are undefined functions (Elixir 1.21 warns), so a generated
body expression short-circuits with `case` instead
(`Mutare.Coverage.Recorder.record_ast/3`).

    iex> Mutare.AST.erlang_call(:"=:=", [1, 2])
    {{:., [], [:erlang, :"=:="]}, [], [1, 2]}

# `erlang_call_args`

```elixir
@spec erlang_call_args(Macro.t(), atom()) :: {:ok, [Macro.t()]} | :error
```

Matches a call `erlang_call/2` built, returning its arguments.

Its inverse, and the one way to recognise a generated operator, so a reader cannot drift
from the builder. Tolerates the `{:__block__, _, [:erlang]}` wrapper a Sourceror reparse of
rendered metamutant source puts around the module atom — `Mutare.Manifest` and
`Mutare.Coverage.Recorder.record?/1` both read reparsed source.

    iex> Mutare.AST.erlang_call_args(Mutare.AST.erlang_call(:andalso, [1, 2]), :andalso)
    {:ok, [1, 2]}

    iex> Mutare.AST.erlang_call_args(quote(do: 1 and 2), :andalso)
    :error

# `key_atom`

```elixir
@spec key_atom(Macro.t()) :: atom() | nil
```

The bare keyword atom of a key node, whether plain (`:do`) or Sourceror-wrapped
(`{:__block__, _, [:do]}`); `nil` for anything that isn't an atom key.

# `keyword_key`

```elixir
@spec keyword_key(atom()) :: Macro.t()
```

Builds a keyword-list **key** node that renders as `key:` rather than `{:key, …}`.

The `format: :keyword` marker is what the renderer keys on; `keyword_label?/1`
is the matching reader. Use it when a mutation emits a fresh keyword entry.

    iex> Mutare.AST.keyword_key(:limit)
    {:__block__, [format: :keyword], [:limit]}

# `keyword_label?`

```elixir
@spec keyword_label?(Macro.t()) :: boolean()
```

Returns whether `node` is the key side of an inline keyword pair.

Sourceror represents `a:` as `{:__block__, meta, [:a]}` with
`format: :keyword`. Such keys are structural labels, not runtime values.

This predicate checks only the inline-keyword marker. Value-context block keys
such as `do` and `else` are handled separately.

# `literal`

```elixir
@spec literal(term()) :: Macro.t()
```

Builds a scalar-literal node with fresh metadata.

Sourceror parses a literal as `{:__block__, meta, [value]}` and may render it
from cached token metadata. Reusing metadata from a parsed literal can therefore
render the original source text after the value has changed. This helper builds a
fresh literal node for replacement values.

String values include a double-quote delimiter. Without it, Sourceror may render a
printable binary as a charlist.

Numbers carry a `:token` derived from the new value. The formatter fetches token
metadata for every numeric literal and can raise when it cannot synthesize one in
an embedded position — for example inside a call node a mutator weaves into
already-parsed source. Because the token is the new value's own text, it can never
re-render stale source.

Negative numbers use the same unary-minus AST shape the parser emits. This keeps
nested negative-number mutations renderable and parseable, for example when a
mutation inside `-0.5` would otherwise format as an invalid `--0.5`.

    iex> Mutare.AST.literal(0)
    {:__block__, [token: "0"], [0]}
    iex> Mutare.AST.literal("mutare")
    {:__block__, [delimiter: ~s(")], ["mutare"]}
    iex> Mutare.AST.literal(-1)
    {:-, [], [{:__block__, [token: "1"], [1]}]}

# `literal_value`

```elixir
@spec literal_value(Macro.t()) :: {:ok, term()} | :error
```

Reads the scalar value from a literal node.

Returns `{:ok, value}` for a number, binary, or atom in either bare scalar
form or Sourceror's `{:__block__, _, [value]}` wrapper. Returns `:error` for
variables, calls, collections, unary-minus literal expressions such as
`Mutare.AST.literal(-1)`, and other non-scalar nodes.

Booleans and `nil` are atoms and are returned as values; filter them separately
when a mutator does not handle them.

    iex> Mutare.AST.literal_value({:__block__, [], [0]})
    {:ok, 0}
    iex> Mutare.AST.literal_value(:foo)
    {:ok, :foo}
    iex> Mutare.AST.literal_value({:x, [], nil})
    :error

# `nil_literal?`

```elixir
@spec nil_literal?(Macro.t()) :: boolean()
```

Whether `node` is a `nil` literal in either bare or Sourceror-wrapped form.

# `numeric_alternatives`

```elixir
@spec numeric_alternatives(number(), number(), number()) :: [{number(), [String.t()]}]
```

The "off-by-one + zero sentinel" alternatives for a numeric literal `value`, exactly as the
built-in `Mutare.Mutators.IntegerLiteral` (`step` 1, `zero` 0) and `Mutare.Mutators.FloatLiteral`
(`step` 1.0, `zero` 0.0) produce them: `value + step` (labelled `"succ"`), `value - step`
(`"pred"`), and `zero` (`"zero"`), as ordered `{value, labels}` pairs.

Two rules ride along, so a custom mutator that mints the same alternatives somewhere core can't
reach (a literal inside a DSL fragment it hosts) stays in step with the built-ins and their
`# mutare:ignore` variant vocabulary: an alternative equal to `value` is dropped (`0` is never
re-emitted for `0`), and alternatives that collapse onto one value are **merged** into a single
pair carrying every label, positioned at the first occurrence — so `1`'s `pred` and its `zero`
sentinel become one `{0, ["pred", "zero"]}` that either qualifier suppresses.

    iex> Mutare.AST.numeric_alternatives(5, 1, 0)
    [{6, ["succ"]}, {4, ["pred"]}, {0, ["zero"]}]
    iex> Mutare.AST.numeric_alternatives(1, 1, 0)
    [{2, ["succ"]}, {0, ["pred", "zero"]}]
    iex> Mutare.AST.numeric_alternatives(0.0, 1.0, 0.0)
    [{1.0, ["succ"]}, {-1.0, ["pred"]}]

Values, not nodes — build each with `literal/1` (and filter first if a position can't take
some of them, e.g. a negative index).

# `opts_get`

```elixir
@spec opts_get([Macro.t()], atom(), term()) :: Macro.t() | term()
```

Returns the value bound to `key` in a Sourceror-form keyword list.

Returns `default` when the key is absent. Keys are read with `key_atom/1`, so
both `[as: B]` and `[{:as, B}]` match.

# `parse!`

```elixir
@spec parse!(String.t()) :: Macro.t()
```

Parse a source string into a Sourceror AST node, raising on a syntax error.

The inverse of `to_string/1`. This is the node shape every `Mutare.Mutator`
receives, so a mutator's tests can parse a snippet the same way the engine does.

    iex> {op, _meta, _args} = Mutare.AST.parse!("a + b")
    iex> op
    :+

# `remote_call`

```elixir
@spec remote_call(Macro.t(), atom(), [Macro.t()]) :: Macro.t()
```

Builds a remote call `mod.fun(args)` around a pre-built callee node.

The general form of `absolute_call/3`: use it when the module reference is
already a node — an `absolute_alias/1` result, a variable, or an alias taken
from the source being mutated.

    iex> Mutare.AST.remote_call(Mutare.AST.absolute_alias([:Kernel]), :==, [1, 2])
    {{:., [], [{:__aliases__, [], [:"Elixir", :Kernel]}, :==]}, [], [1, 2]}

# `render_opts`

```elixir
@spec render_opts(keyword()) :: keyword()
```

`Sourceror.to_string/2` options with the formatter lookup pinned off — `to_string/2`'s
defaults, for the few places that render through Sourceror directly.

# `sentinel_alias`

```elixir
@spec sentinel_alias() :: [atom()]
```

The survivor sentinel as a module-alias path (`Mutare.Mutant`).

# `sentinel_atom`

```elixir
@spec sentinel_atom() :: atom()
```

The survivor sentinel as an atom (`:mutare`).

# `sentinel_string`

```elixir
@spec sentinel_string() :: String.t()
```

The survivor sentinel as a string: a value distinctive enough to flag a
surviving mutant in a report, yet unlikely to occur in real code. The literal
families (`StringLiteral`, `ReturnValue`, …) all substitute it, so the single
word lives here.

# `string_binary?`

```elixir
@spec string_binary?(Macro.t()) :: boolean()
```

Whether a `{:<<>>, meta, _}` node is really a **string**, not a `<<…>>` bitstring.

Sourceror parses an interpolated string (`"a#{x}b"`, or an interpolated heredoc) into a
`<<>>` node, distinguished from a true `<<…>>` bitstring literal by a `:delimiter` key cached
in its metadata. The string- and bitstring-literal families route on it with opposite intent
— `StringLiteral` mutates these (an interpolated string), `BitstringLiteral`/`BitstringSpec`
skip them (they apply to bitstring literals). A non-`<<>>` node is never a string binary.

    iex> Mutare.AST.string_binary?({:<<>>, [delimiter: ~s(")], ["hi"]})
    true
    iex> Mutare.AST.string_binary?({:<<>>, [], [1, 2]})
    false
    iex> Mutare.AST.string_binary?({:__block__, [], [:atom]})
    false

# `strip_comments`

```elixir
@spec strip_comments(Macro.t()) :: Macro.t()
```

Drops Sourceror's `:leading_comments`/`:trailing_comments` from every node of `ast`.

Sourceror attaches each comment to one node — a trailing `# …` to the leftmost leaf of
its line, a comment before `end` to the enclosing form — and `Sourceror.to_string/1`
renders whatever the subtree carries. Since a mutation reuses the original's operands,
the comment would ride into the mutated node too. `to_string/1` strips it for you; call
this directly when a parsed node is re-emitted into generated code where the comment
should not repeat.

    iex> {:a, meta, nil} = "# note\na" |> Mutare.AST.parse!() |> Mutare.AST.strip_comments()
    iex> Keyword.take(meta, [:leading_comments, :trailing_comments])
    []

# `to_string`

```elixir
@spec to_string(
  Macro.t(),
  keyword()
) :: String.t()
```

Render an AST node back to formatted source, the inverse of `parse!/1`.

Comment metadata is stripped first (`strip_comments/1`), so a node taken from parsed
source renders as its code alone — never the `# …` text Sourceror parked on it.

    iex> Mutare.AST.to_string(Mutare.AST.literal("mutare"))
    ~s("mutare")
    iex> "a > x # note" |> Mutare.AST.parse!() |> Mutare.AST.to_string()
    "a > x"

`opts` are passed through to `Sourceror.to_string/2`; `line_length: :infinity` renders
without fits-based line breaks (structural breaks — `do`/`end`, clauses — still apply).

Local calls always render with parentheses (`locals_without_parens: []`), whatever the
settings in the target project's `.formatter.exs`. Left to its default, `Sourceror.to_string/2`
evaluates that file through `Mix.Tasks.Format` on **every** call — its `import_deps`
and plugins, inside Mutare's own process — and Mix can reject that evaluation mid-run (see NOTES
"Rendering never consults the target's formatter configuration"). Both spellings are
the same code, and the metamutant only has to compile.

# `unwrap_literal`

```elixir
@spec unwrap_literal(Macro.t()) :: Macro.t()
```

Removes one Sourceror literal wrapper.

A single-element `{:__block__, _, [inner]}` returns `inner`; any other node is
returned unchanged. Unlike `literal_value/1`, this helper does not require the
inner term to be a scalar literal and does not wrap the result in `{:ok, _}`.

    iex> Mutare.AST.unwrap_literal({:__block__, [], [:persistent_term]})
    :persistent_term
    iex> Mutare.AST.unwrap_literal(:persistent_term)
    :persistent_term

# `update_do_block`

```elixir
@spec update_do_block(Macro.t(), (Macro.t() -&gt; Macro.t())) :: Macro.t()
```

Map over a keyword list, replacing the value of the `:do` entry with `fun.(value)`.

Recognises both Sourceror's keyword-block key (`{:__block__, _, [:do]}`)
and a plain `:do` (via `key_atom/1`), preserves the original key node (so its
`format: :keyword` marker survives for the renderer), and leaves every other entry
— and a non-list argument — untouched.

# `update_do_block_reduce`

```elixir
@spec update_do_block_reduce(Macro.t(), acc, (Macro.t(), acc -&gt; {Macro.t(), acc})) ::
  {Macro.t(), acc}
when acc: var
```

`update_do_block/2` threading an accumulator: `fun.(value, acc)` returns
`{new_value, acc}`, like `Enum.map_reduce/3`. Non-`:do` entries (and a non-list
argument) pass the accumulator through unchanged.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
