# `Mutare.Manifest`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.4.3/lib/mutare/manifest.ex#L1)

A map from mutant ids to their generated line ranges in a rendered metamutant,
and a list of every place the generated code names a mutant id.

`Mutare.Transform` writes the metamutant; this is what `Mutare.Poison` reads
back. It is built **lazily** (`from_source/2`), by `Mutare.Poison` on a failed
compile, only for the file(s) the error names — not eagerly during the scan,
where re-parsing every rendered metamutant was the scan's dominant cost yet is
read only when a compile actually fails (rare — built-in mutators are
compile-safe). `Poison` memoizes it within one recovery so a file faulting on
several lines is walked once.

The one exception is `verify_invariants: true` (`mix mutare --verify-invariants`):
the transform then builds a manifest for every file it renders and compares its
`:mentions` with the mutants it recorded, raising `Mutare.InvariantError` on a
mismatch.

**`Mutare.Poison`** uses each mutant's *generated ranges* — the metamutant line
spans of the code that exists only because of that mutant, so a compile error's
line maps back to the corresponding mutant id.

(Coverage is recorded separately: the metamutant self-records coverage at runtime
— see `Mutare.Coverage.Recorder` — keyed by mutant id directly, so there is no
metamutant `{module, line}` location to precompute.)

## Why ranges, not a single line

Poison recovery used to match a compile error's line against the *start line of
a selector clause body* only. That missed every poison whose bad code isn't on
that exact line:

  * **lifted mutations** — a guard/head-pattern mutant's code appears in a generated
    `defp <base>(mutare_active, …) when mutare_active === <id> …` clause, not in
    the dispatcher (which only forwards). The error points into that gated clause,
    away from the dispatcher;
  * **multiline bodies** — an in-place mutant whose body spans several lines can
    fault on any of them, not just the first;
  * **structural errors** — the compiler sometimes points at the surrounding
    `case` rather than a clause body.

So we record, per mutant, the *full* line ranges of its generated code:

  * each selector clause body (`<id> -> <mutated>`) — catches in-place mutants,
    including multiline bodies;
  * each lifted mutant clause (gated `when mutare_active === <id>`) — catches a
    guard/head-pattern poison, whose code is outside the dispatcher. A clause shared by
    several guard mutants (one `when` alternative each) gives every alternative to its own
    mutant, and the whole clause — head patterns and the one shared body — to all of them;
  * the whole selector `case`, attributed to *all* the mutant ids it hosts — the
    coarse fallback for a structural error that points at the `case` itself.

`ids_at_line/2` resolves an error line by **narrowest containing range**: a line
inside a specific clause/def range maps to that one mutant; only when nothing
narrower contains it does the whole-`case` fallback fire (dropping every mutant
the `case` hosts — a bounded over-drop that still recovers the build, never the
old "couldn't map it → abort").

## Clean copies

A clean region (`Mutare.Transform.CleanRegion`) keeps a function's uninstrumented source
beside the instrumented code. That copy belongs to no mutant, and the transform copies
any source without asking whether it survives the copy, so the copy's lines are recorded
too, under the region's identity — the id interval its guard carries (`:clean`,
`t:clean_span/0`). `blame_at_line/2` weighs both kinds by the same narrowest-range rule,
and poison recovery drops a blamed region as it drops a blamed mutant. A lifted group's
clean clauses sit outside their dispatcher, under the name `CleanRegion.read/2` reports;
each such definition is a span of that region.

Ranges are in **metamutant line space**, which only exists after rendering, so
the manifest is built by re-parsing the rendered metamutant and ranging its
generated nodes with `Sourceror.get_range/1`, recognising selectors via
`Mutare.Metamutant.subject?/2`. A selector inside a function body reads the
file's **dispatch variable** rather than `:persistent_term` directly, and a
lifted or tupled mutant clause is gated on it (`<var> === <id>`) — so that name
must be supplied to the reader. It is per file (`:mutare_active`, or a salted variant
when the source already uses that identifier), chosen by the transform and
handed out with the metamutant (`Mutare.Transform.Result.dispatch_var`,
`Mutare.Schema`'s `:dispatch_vars`); `from_source/2` takes it alongside the source.

The re-parse uses Elixir's own `Code.string_to_quoted!` (with `:token_metadata`
+ `:columns`), **not** `Sourceror.parse_string!`. `get_range/1` only needs that
token metadata (`:line`/`:column`/`:closing`/`:end`/`:end_of_expression`), which
the stdlib parser already produces; what `Sourceror.parse_string!` *adds* is a
comment-merging pass that is quadratic on a large metamutant (a lifted 1.8 MB
file re-parses in ~0.6 s here, versus minutes for Sourceror) and that the
manifest never reads. A `:literal_encoder` reproduces Sourceror's
`{:__block__, meta, [literal]}` wrapping so the recognisers — already tolerant of
both shapes — accept the resulting nodes; the two parses yield identical
ranges. (Sourceror is still the *renderer*; only this readback parse changed.)

# `clean_range`

```elixir
@type clean_range() :: {pos_integer(), pos_integer()}
```

A clean region's identity within its file: the inclusive interval of file-local mutant ids
its guard carries. Regions claim disjoint ids, and ids are stable across poison rebuilds.

# `clean_span`

```elixir
@type clean_span() :: %{
  range: clean_range(),
  part: :branch | :definition,
  lo: pos_integer(),
  hi: pos_integer()
}
```

A line range holding a clean region's uninstrumented copy, under the region's identity. `:part` is `:branch` for the clean branch of
the region `case` — an in-place body, or a lifted dispatcher's call to its clean clauses —
and `:definition` for one relocated clean clause.

# `mention`

```elixir
@type mention() :: %{
  kind: :branch | :exclusion | :record,
  id: pos_integer(),
  within: [pos_integer(), ...] | nil
}
```

One place where the generated code names a mutant id:

  * `:branch` — code that runs only while `id` is active: a selector clause body, a gated
    clause (lifted, tupled, `fn`, `receive`), or a gated guard alternative;
  * `:exclusion` — an original clause stepping aside while `id` is active (`<var> =/= <id>`,
    or the range form for a run of ids). A dropped clause leaves only this trace;
  * `:record` — a coverage record listing `id`.

`within` names the innermost mutant branch enclosing the mention — the ids under which
that branch runs — or is `nil` outside every branch. It is a list because a clause shared
by several guard mutants runs its one body under any of them; every other branch has one
id. Code inside a branch runs only while one of its ids is active, so a run that activates
`id` alone reaches the mention only when `within` is `nil` or holds `id`.

# `region`

```elixir
@type region() :: %{ids: [pos_integer()], lo: pos_integer(), hi: pos_integer()}
```

A generated line range and the mutant ids whose code occupies it.

# `t`

```elixir
@type t() :: %Mutare.Manifest{
  ast: Macro.t() | nil,
  clean: [clean_span()],
  mentions: [mention()],
  regions: [region()]
}
```

One file's manifest:

  * `:regions` — generated line ranges, for mapping a compile error back to a mutant.
  * `:mentions` — every generated mention of a mutant id, in source order, for
    `Mutare.Transform.Invariants` to check against the mutants the transform recorded.
  * `:clean` — the line ranges of clean regions' uninstrumented copies, in source order.
  * `:ast` — the parsed metamutant the regions were ranged over, retained so the
    macro-expansion fallback (`ids_in_named_calls/2`) can range a blamed macro's calls in
    the same tree rather than parse the file a second time. `nil` on a hand-built manifest.

# `blame_at_line`

```elixir
@spec blame_at_line(t(), pos_integer()) :: %{
  ids: [pos_integer()],
  clean: [clean_range()]
}
```

What a compile error on `line` is blamed on: the mutant ids whose generated code occupies
it, and the clean regions whose uninstrumented copy does — `%{ids: ids, clean: ranges}`.

Both kinds compete by the **narrowest containing range**, as in `ids_at_line/2`. A clean
copy holds no mutant code, so in practice a line has one kind of owner; the shared rule
only settles nesting.

## Examples

    iex> manifest = %Mutare.Manifest{
    ...>   regions: [%{ids: [1], lo: 3, hi: 4}],
    ...>   clean: [%{range: {1, 2}, part: :branch, lo: 6, hi: 9}]
    ...> }
    iex> Mutare.Manifest.blame_at_line(manifest, 7)
    %{ids: [], clean: [{1, 2}]}
    iex> Mutare.Manifest.blame_at_line(manifest, 3)
    %{ids: [1], clean: []}
    iex> Mutare.Manifest.blame_at_line(manifest, 5)
    %{ids: [], clean: []}

Nested and overlapping owners:

    iex> manifest = %Mutare.Manifest{
    ...>   regions: [
    ...>     %{ids: [1, 2, 3], lo: 3, hi: 8},
    ...>     %{ids: [1, 2], lo: 5, hi: 6},
    ...>     %{ids: [2, 3], lo: 5, hi: 6}
    ...>   ]
    ...> }
    iex> Mutare.Manifest.blame_at_line(manifest, 5)
    %{ids: [1, 2, 3], clean: []}
    iex> Mutare.Manifest.blame_at_line(manifest, 4)
    %{ids: [1, 2, 3], clean: []}
    iex> Mutare.Manifest.blame_at_line(%{manifest | regions: Enum.take(manifest.regions, 2)}, 5)
    %{ids: [1, 2], clean: []}

# `from_source`

```elixir
@spec from_source(String.t(), atom()) :: t()
```

Build a manifest from one file's rendered metamutant source and its dispatch variable.

Re-parses (for `Sourceror.get_range/1`) and walks the tree once, attributing
every selector clause, lifted private definition, and selector `case` to the
mutant id(s) it belongs to, and collecting every mention of an id (`t:mention/0`). `dispatch_var` is the name the transform chose for this
file (`Mutare.Transform.Result.dispatch_var`): the hoisted selectors read it as their
subject and the gated mutant clauses compare it, so it is what makes them recognisable.
The parse is kept on the manifest (`:ast`): one failed compile can need both
attributions of the same file, and `Mutare.Poison` holds one manifest per file for the
round.

## Examples

    iex> source = "defmodule Demo do\n  def add(a, b), do: a + b\nend\n"
    iex> result = Mutare.transform_string(source, mutators: [:arithmetic])
    iex> %Mutare.Manifest{regions: regions} =
    ...>   Mutare.Manifest.from_source(result.metamutant, result.dispatch_var)
    iex> regions == []
    false

# `ids_at_line`

```elixir
@spec ids_at_line(t(), pos_integer()) :: [pos_integer()]
```

Mutant ids whose generated code occupies `line`.

Resolved by **narrowest containing range**: a specific clause/def range beats the
coarse whole-`case` fallback, so a precise error drops exactly the offending
mutant; a structural error that only the `case` range contains drops every mutant
it hosts. Returns `[]` when no generated code spans `line`.

## Examples

    iex> manifest = %Mutare.Manifest{
    ...>   regions: [
    ...>     %{ids: [1, 2], lo: 3, hi: 8},
    ...>     %{ids: [1], lo: 5, hi: 5}
    ...>   ]
    ...> }
    iex> Mutare.Manifest.ids_at_line(manifest, 5)
    [1]
    iex> Mutare.Manifest.ids_at_line(manifest, 4)
    [1, 2]
    iex> Mutare.Manifest.ids_at_line(manifest, 99)
    []

# `ids_in_named_calls`

```elixir
@spec ids_in_named_calls(t(), MapSet.t(atom())) :: %{optional(atom()) =&gt; MapSet.t()}
```

Mutant ids inside a call to one of `names`, grouped by that call's function name.

The **macro-expansion fallback**'s attribution (`Mutare.Poison.macro_poison/4`): when a
mutation splices a selector `case` into an argument a macro rewrites at compile time, the
macro raises during expansion and the compiler reports the macro *call* line — which no
region covers — so `ids_at_line/2` finds nothing. Given the macro name from the compiler's
`expanding macro:` frame, this instead finds every call of that name in the rendered
metamutant, takes its **full** line range (`Sourceror.get_range/1`, to the closing
delimiter — so a literal argument on its own line is still spanned), and collects the ids of
every region contained in it.

Works in **metamutant space**: the rendered source is what the compiler read, so the ids
found inside a blamed macro's span are exactly the ones that could have poisoned it — no
mapping back to original-source coordinates is needed, or possible. Reads the manifest's
retained `:ast` (a `from_source/2` manifest, whose regions were ranged with the file's
dispatch variable), so the file is parsed once for both attributions. Returns
`%{fun_atom => MapSet.t()}`, empty when nothing matched.

---

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