# `Mutare.Transform`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.5.1/lib/mutare/transform.ex#L1)

Source → metamutant transform, expressed as an explicit pipeline over a small
intermediate representation.

Rather than walk every node and then *subtract* the positions that must not be
mutated (the old blacklist), the transform classifies each node's context
*positively*, builds a plan, then renders it. The plan is three typed pieces:

  * `Mutare.Transform.ModulePlan` — a statement sequence (a module body)
    classified into items: a clause group to **lift**, a clause group to keep
    **in place**, or any other **statement**. This is "module planning",
    separated from emission.
  * `Mutare.Transform.FunctionPlan` — one liftable clause group: its signature,
    its clauses, a single shared *tagged* clause group, and the typed lifted
    candidates (`Candidate.Lifted` / `Candidate.Drop`) it admits.
  * `Mutare.Transform.Candidate` — the typed, pre-id description of a single
    mutant. One struct per legal kind (see that module's moduledoc for the current
    set), so the redundant `context`/`kind`/`operation` triple (and its illegal
    combinations) is gone.

The stages, run per subtree:

  1. **Analyze + classify** — `analyze/3` is one context-threaded recursive
     descent: it *names the context* of each position as it descends and, for
     every node a mutator recognises *in a mutating context*, attaches a typed
     `Candidate.InPlace` to the node's own metadata (`meta[:mutare]`). Mutators
     run **once**, here. Routing is positional (the spec side of a `::` goes one
     way, the value side another), which is why it can't be a flat
     `Macro.traverse` accumulator. Three contexts are threaded — `:runtime`
     (mutate, → in-place; `:guard`/`:clause_drop`/head-pattern literals come from
     the lift path), `:pattern` (don't mutate in place, but keep descending so
     default-arg values and `size()` args are reached), and `:scaffold` (a known
     compile-time module-level statement: descend but never mutate its own
     expressions — they run once at compile time, so a selector there is inert —
     yet still reach any explicit `def` body, which flips back to `:runtime`);
     the rest (`:compile_time`, `:spec`, `:guard`,
     `:capture_arity`) are recognised and pruned, producing no candidate.
  2. **Plan** — a statement sequence is grouped into a `ModulePlan`; each
     liftable clause group becomes a `FunctionPlan` carrying its lifted
     candidates. No ids are assigned yet.
  3. **Assign** — emission walks the plan and the annotated tree bottom-up and
     hands each candidate the next mutant id. Ids are assigned in post-order DFS
     and the counter advances even for ignored, unselected, and `:skip_ids`
     mutants. Ignore directives are matched against each recorded site's line,
     family, and variant before deciding whether to emit its code.
  4. **Emit** — an in-place candidate becomes a tail-position selector `case`; a
     `FunctionPlan` becomes one private function (threading the active id as an
     extra arg) behind a dispatcher, each mutant a single guarded clause. Ignored,
     unselected, and poisoned mutants retain their sites but emit no code.
     A function holding two or more selector sites also keeps its source beside
     the instrumented code, as a **clean region**: a lifted group gains a copy of
     its original clauses, a clause that stays in place a second `:do` body, and
     one decision on the already-read id sends a mutation elsewhere to the source.
     Baseline/probe and the region's full id interval retain the instrumented
     path. Any source may be copied: a copy that fails to compile is attributed
     to its region, which poison recovery then drops (`:skip_regions`). A copied
     call still enters its callee through that callee's own dispatcher.
  5. **Render** — annotations are stripped and the tree is rendered to source
     (with a Sourceror keyword-block workaround).

Carrying the `Candidate.InPlace` in the node's *own* metadata is what lets
emission find "this exact node" without a fragile `{line, column}` identity:
metadata is intrinsic to the node and rides through any `Macro` rebuild, so
duplicate subtrees can never collide.

## In-place selector (body expressions)

An operator inside a body is wrapped in a tail-position `case` reading the
active mutant id from `:persistent_term`:

    # source:   total >= threshold
    case :persistent_term.get(:mutare_active, 0) do
      17 -> total > threshold     # mutant 17:  >= → >
      _  -> total >= threshold    # baseline + every other mutant
    end

Substituting a node with a value-equivalent `case` preserves its position, so
tail calls stay tail calls (LCO). Nested sites work because the catch-all holds
the *transformed* children, reachable whenever an outer mutant is inactive.

A **pipe stage** is analyzed as the call it is sugar for (`left |> stage(args)` as
`stage(left, args)` — `WrittenPipe`), so no selector ever lands on the
right side of a pipe, where a `case` is illegal. Emission binds the piped value once, in a
one-shot closure invoked on it (`SelectorDelivery`):
`lhs |> (fn v -> case … (each branch pipes `v`) … end).()`. Each branch references a cheap
variable — keeping a chain of mutated stages **linear** in the rendered source, where
distributing `lhs` into every branch would copy the whole upstream chain per branch. The
Site still records the stage as written, so the diff is unchanged.

The closure evaluates the piped value ahead of the stage, as an ordinary call does its first
argument — and a call is ordinary in every respect its route does not address
(`Mutare.CallRouting`, "Ordinary calls"). So it is used when the stage's first position is a
value (unrouted, `:expression` or `:interior`); one routed `:lazy_expression`, or as syntax,
is never evaluated ahead of the call. A mutant that moves or drops the piped value goes in a
selector around the closure instead, evaluating its own expression in its own order. No
stage reaches emission as a pipe: the rewritten calls are direct calls in the emitted tree
(their mutant branches hold the as-written arguments, the catch-all the emitted ones), and
`Render` spells each as the pipe it was written as. Code left as written — a `:raw`
argument, a `:skip`ped call — keeps its pipe untouched. Only `Kernel`'s `|>` is treated as a
pipe at all; one a module displaced is a call to that module's operator.

## Function lifting + dispatcher (guards, dispatch)

A `case` is illegal in a `when` guard, and guards drive dispatch *across*
clauses, so guard mutations (and head-pattern / clause-drop mutations) cannot be
done in place. Instead the whole clause group becomes **one** private function
that takes the active mutant id as an extra first arg (`mutare_active`); the
public `f/arity` becomes a dispatcher that reads the id and forwards. Each source
clause is emitted **once** as an original gated `when mutare_active !== <id>` for
every mutant that overrides/drops it; each mutant adds a **single** clause gated
`when mutare_active === <id>`, placed before the original it replaces:

    def f(a) do
      mutare_active = :persistent_term.get(:mutare_active, 0)
      __mutare_f_1_g1(mutare_active, a)
    end
    defp __mutare_f_1_g1(mutare_active, a) when mutare_active === 5 and a > 1, do: ...  # mutant 5: guard >= → >
    defp __mutare_f_1_g1(mutare_active, a) when mutare_active !== 5 and a >= 1, do: ... # original (in-place applies here)

Exactly one clause wins for any `(id, args)`: the mutant when its id is active and
its head/guard match, else the original. This is **per-clause**: a mutant touching
one clause no longer copies the other N−1, so a group with C clauses and M mutants
emits ~C+M clauses, not C×M (see NOTES "lifting blowup"). In-place selectors live
only in the *original* clauses (and non-lifted code); a mutant clause reuses the
raw body — sound because exactly one mutant is ever active. The public `f/arity`
is unchanged at the module boundary.

Ranges are captured against the *original* AST, which is what the diff report
patches against.

## Implementation modules

`Mutare.Transform.{ModulePlan,FunctionPlan,Candidate}` define the intermediate representation —
the plan structs and pure discovery (chunking clauses, finding guard/drop
candidates). This module implements the top-level emission traversal and coordinates function
lifting; focused delivery modules implement the smaller specialized paths, with shared
selector mechanics factored through `SelectorEmit`.

Focused helper modules keep the pure node-building and the smaller specialized
delivery paths out of this file:

  * `Mutare.Transform.ClauseAST` — the shared `def`/`defp` clause shape and the
    primitives that navigate it (head/args/guards/`when`), used by both this
    module and `FunctionPlan`.
  * `Mutare.Transform.GuardBuild` — the dispatch guards (`<var> === <id>` gate,
    exclusion, `and`-into), shared by the lifted and `case` paths.
  * `Mutare.Transform.LiftedEmit` — the dispatcher + gated base clauses for a
    lifted group (the assembly half of `emit_function_plan/2`).
  * `Mutare.Transform.CleanRegion` — the one decision that sends a mutation
    elsewhere to a function's uninstrumented source, the policy for when that
    repays its code, and the reader that lets a failing copy be blamed on its region.
  * `Mutare.Transform.CaseClauseEmit` — the tuple-the-scrutinee delivery for
    per-clause `case` pattern/guard mutants.
  * `Mutare.Transform.FnClauseEmit` — per-clause anonymous-function delivery,
    capturing the selector and recording head/guard coverage at closure creation.
  * `Mutare.Transform.ReceiveClauseEmit` — per-clause receive delivery, preserving
    native mailbox order and one timeout evaluation; clause guards are shared with
    anonymous functions through `Mutare.Transform.ClauseVariants`.
  * `Mutare.Transform.ClauseGuardEmit` — guard-only delivery for the clauses that can
    host no extra clause (`with`/`for` `<-`, `with`/`try` `else`, `try` `catch`,
    `for … reduce:` `do`): the guard becomes a gated guard sequence in place.
  * `Mutare.Transform.RescueEmit` — shares protected bodies across rescue mutants
    when native handler dispatch can reuse an existing exception binding; other
    shapes retain whole-try selection.
  * `Mutare.Transform.BindingEscapeEmit` — the tuple-export delivery for binding
    escaping `=` matches and known macros.
  * `Mutare.Transform.HostedEmit` — the selector-host delivery for hosted DSL
    fragments.
  * `Mutare.Transform.ImportWitness` — the dead-code import witness spliced
    alongside a mutated bare imported call.
  * `Mutare.Transform.CoverageEmit` — scope-aware coverage gate emission and
    binding dependencies; delivery paths retain ownership of recording positions.
  * `Mutare.Transform.SelectorEmit` — the shared id/site claim, selector
    subject, catch-all coverage branch, and ordinary selector-case assembly.

# `count_string`

```elixir
@spec count_string(
  String.t(),
  keyword()
) :: non_neg_integer()
```

Count the mutants a source would produce, **without rendering** the metamutant.

The count is the number of ids claimed by `transform_string/2`:
`next_id - start_id`, which is also the number of returned public mutants. It is computed
by running the same analysis, planning, and emit pipeline as `transform_string/2`,
but without rendering the final metamutant source.

`Mutare.Schema` uses this for its two-phase build. First it counts each file, then
assigns each file a stable `:start_id`, then renders files in parallel. The count
does not depend on the caller's `:start_id` or `:skip_ids`; skipped ids still
claim their position so the later render assigns the same span.

Accepts the same options as `transform_string/2` and raises the same parser
exceptions for invalid source.

# `render_sites`

```elixir
@spec render_sites(
  String.t(),
  keyword()
) :: [Mutare.Site.t()]
```

Rebuild a source's sites with `original_code` and `mutated_code` populated.

This skips metamutant rendering. It exists for deferred
diff rendering: a `mix mutare` scan can set `:render_site_code` to `false`,
then `Mutare.Runner.Hydrate` can call this later for the few sites that need to
be displayed.

Call it with the same options used for the original scan, including the file's
`:start_id`. Because the transform is deterministic for one source, the ids and
rendered code match an eager `transform_string/2` run. This function always
enables `:render_site_code`, regardless of the caller's option value.

Accepts the same options as `transform_string/2` and raises the same parser
exceptions for invalid source.

# `transform_string`

```elixir
@spec transform_string(
  String.t(),
  keyword()
) :: Mutare.Transform.Result.t()
```

Transform a source string into a stable public result DTO.

Returns `%Mutare.Transform.Result{}` with the rendered metamutant source, public mutant
descriptions, and `next_id`.

`next_id` is the first mutant id left unassigned — what the next file in a
schema should start from. It equals `:start_id` when nothing was mutated, so
the caller never has to recover it from the last site.

Options:

  * `:file` — path recorded on each site (default `"nofile"`)
  * `:mutators` — list of mutator entries (family atoms, modules, `{module, opts}`
    pairs, or `Mutare.Mutator.Spec`s); defaults to the full built-in set
  * `:call_routes` — list of call-route entries (`{module, name, arity, treatment}` /
    `{module, name, treatment}`, see `Mutare.CallRouting`) that route a call's arguments
    specially, or skip the call outright; merged with the built-ins and routing capabilities on
    enabled mutators/extensions. Defaults to `[]`.
  * `:argument_marks` — list of `{module, function, arity, positions, label}` declarations
    (the shape `c:Mutare.Mutator.argument_marks/1` returns) marking extra positions for the
    mutators that read `label`; merged with the enabled mutators' own declarations. Defaults to
    `[]`.
  * `:extensions` — list of non-mutating extension modules, each implementing
    `Mutare.CallRouting`, `Mutare.UseExpansion`, or both; entries may be bare modules or
    `{module, opts}` pairs. Static routes merge into the registry and `expand_use/3` overrides
    feed `use`-expansion (the
    extension's `opts` ride along to `expand_use/3`'s context). Defaults to `[]`.
  * `:start_id` — first mutant id to assign (default `1`)
  * `:runtime_namespace` — internal schema plumbing: when set to a nonempty
    file string, emit local ids from one under that namespace while Sites and
    `:skip_ids`/`:emit_ids` retain the `:start_id` report range. Omitted by the
    public standalone path, which keeps integer runtime selection.
  * `:expand_uses` — when `true` (the default), expand module-level `use` statements with
    static args and feed their injected `import`/`alias` directives into resolution (see
    `Mutare.Transform.Uses`); `false` freezes the pre-expansion behaviour (and, with it, any
    extension `use`-expansion overrides)
  * `:warnings` — when `true` (the default), print advisory warnings: suspect but
    non-fatal extension behaviour (currently: a `:routing` classifier returning
    `{:keyword, …}` for a non-keyword argument) and the lifting advisories (a
    `:skip_lifting` match, non-consecutive / metaprogrammed / delegated clause groups).
    Callers that re-run the pipeline over a source already scanned pass `false` so each
    warning is printed once — `Mutare.Schema`'s render phase (the count phase warned) and
    `render_sites/2` (report-time re-derivation).
  * `:verify_invariants` — when `true`, check the result before returning it and raise
    `Mutare.InvariantError` listing every violation: the rendered metamutant must parse; every
    mutant the result records as live must have a branch that runs when it alone is active, and
    a coverage record; the generated code must name no other id; no live mutant may render
    identically to its original; and emitting the source a second time must reproduce the
    first pass. Meant for developing mutators, hosts, and extensions; the checks add roughly a
    sixth to the transform's cost, since the second pass stops before the render. Defaults to
    `false` (`Mutare.Test`'s source helpers default it to `true`).

---

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