# `Mutare.Transform`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.2.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. `CleanPath` defines what may be copied; 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.

One position the selector `case` is *not* legal in: the right side of a pipe.
`x |> case … end` parses but fails to compile (`Kernel.|>/2` cannot pipe into a
`case`), so when a mutated node is a **pipe stage**, emission lifts the selector
out of the pipe into a one-shot closure invoked on the piped value
(`PipeEmit.hoist/2`): `lhs |> (fn v -> case … (each branch pipes `v`) … end).()`. The
piped value is computed once (it stays the pipe's LHS) and bound to `v`, so 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 and blow up exponentially. The Site still records
the bare stage, so the diff is unchanged.

## 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, and the policy for when that
    repays its code; `Mutare.Transform.CleanPath` is the positive, scope-tracking
    contract for which source may be copied.
  * `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).

---

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