# Changelog
## [0.4.6.0] - 2026-07-29
### Added
* A `skip-calls-to` config key on `ConstConstructor`, the same key `ElideCall`
and `SwitchFunctionArguments` already take. A function whose result is
always the same constructor -- a delegating `sqlType Proxy = sqlType (Proxy
:: Proxy Text)`, say -- makes every call to it an equivalent, unkillable
mutant. Whether a function is like that is a semantic property the plugin
cannot detect, so listing its name suppresses the operator at every call to
it. The definition itself is still mutated: the key skips calls.
### Fixed
* `ConstConstructor` no longer replaces a binding that is defined as a nullary
constructor with that same constructor. `Data.Map.empty` is `Tip`, so
rewriting it to `Tip` produced an unkillable mutant at every use of it (and
of `Set.empty`, `Seq.empty`, and any `emptyFoo = NoFoo` of your own). The
operator already declined this no-op where the constructor is written out;
it now also recognises it through a name, by reading the unfolding GHC
recorded for the binding. A binding in the module being compiled has no
unfolding yet, so an alias defined locally is still mutated to itself.
## [0.4.5.0] - 2026-07-29
### Added
* A `ConstConstructor` mutation operator: for an expression whose type has a
nullary constructor, or a function returning one, it emits one mutant per
nullary constructor of that type. This covers both an enumeration (`data
ABC = A | B | C`, where every value can be switched to every other) and a
type that merely has a constant (`data MyMaybe a = MyNothing | MyJust a`,
where `MyNothing` is the only replacement) — generalising what `ConstBool`
and `BoolLit` do for `Bool` and what `ConstNothing` and `MaybeOp` do for
`Maybe`. Those three types (plus lists, whose `[]` `ConstEmptyList` and
`ListLit` already cover) are excluded so no mutation is produced twice, as
is the alternative replacing a constructor with itself, and any type with
fewer than two constructors (including `()`), which has no alternative
value to offer.
### Fixed
* A parenthesised expression is no longer mutated twice. Every operator that
fired on the expression inside also fired on the `HsPar` node around it,
recording two mutations that differ only in span and that no test can tell
apart.
* An expression under an inline type signature is now instrumented. The
walker did not descend into `e :: T`, so nothing inside such an expression
was a mutation site for any operator.
* The manifest preview for a constant-function mutation on an infix operator
of arity 3 or more no longer drops the operands. It rendered `a <+> b` as
`(\_ _ _ -> v)`, which does not even have the type of the expression it
replaces; the mutation applies the constant function to both operands, so
the preview now reads `(\_ _ _ -> v) (a) (b)`.
## [0.4.4.0] - 2026-06-20
### Fixed
* Expressions of unlifted or representation-polymorphic type are no longer
instrumented. `ifMutation`'s type variable has kind `Type`, so wrapping an
`Int#`-typed expression (e.g. in Alex/Happy-generated lexers) built
ill-kinded `ifMutation @Int#` Core that Core Lint rejects and that
miscompiles without it.
## [0.4.3.0] - 2026-06-20
### Fixed
* `MaybeOp` and `ConstNothing` no longer emit a bare `Nothing :: forall a.
Maybe a`. The un-instantiated `forall` made the surrounding `ifMutation`
wrapper ill-typed Core, which — with Core Lint off — miscompiled the
instrumented site and, for some element types, segfaulted at runtime. The
element type is now applied, mirroring how `ConstEmptyList` threads its
element type.
## [0.4.2.0] - 2026-06-19
### Added
* A `TupleSwap` mutation operator: for a boxed tuple whose two components have
the same type, it emits one mutant per same-typed pair that swaps that pair.
It is the tuple-syntax counterpart to `SwitchFunctionArguments`, which only
sees application spines and never the `ExplicitTuple` node. Tuple sections
(which carry a missing component and are really functions), pairs with
identical source text (whose swap is a no-op), and unboxed tuples (whose
unlifted kind the lifted `ifMutation` wrapper cannot carry) are skipped.
## [0.4.1.0] - 2026-06-19
### Fixed
* Mutants whose replacement text contains a `/` are now activated (and so
killable) instead of always surviving. A `MutationId` renders and parses as
`/`-separated parts, so its parts must contain no `/`, yet the id embedded the
replacement string (raw source text) as a part. A `SwitchFunctionArguments`
swap of e.g. `stripPrefix "/nix/store/" t` has replacement text
`t "/nix/store/"`, which contains a `/`. The compiled `ifMutation` carried
that id intact, but the driver renders the id into `MUTATION_ACTIVE` and the
runtime parses it back by splitting on `/`: the embedded `/` split into extra
parts, the parsed id never equalled the compiled one, and the mutation was
never made active. It therefore survived even when a test killed it, while
same-site operators with a `/`-free replacement (e.g. `ConstNothing`'s
`Nothing`) were killed normally. (The coverage phase still recorded the
intact id, so the mutant showed up as `survived`, not `uncovered`.) The id
is now the structural key `[module, operator, line, colStart, colEnd,
altIndex]`; none of those parts can contain a `/`, and the index already
disambiguates alternatives at one span. The replacement text remains in the
manifest's `replacement` field for humans.
## [0.4.0.0] - 2026-06-17
### Added
* A `RemoveClause` mutation operator: for a function binding with two or more
equations, it emits one mutant per equation that removes that equation.
Because a clause is a binding-level `Match` rather than an expression, the
removal is implemented by prepending an `ifMutation mid False True` guard to
the clause: when the mutation is active every guard of the clause fails, so
the clause matches no input and control falls through to the next equation
(or to a non-exhaustive `MatchFail` for the last matching clause). The guard
sits after the clause's patterns, so coverage is attributed only to tests
that actually reach the clause. Disable it like any other operator via
`disabled-mutations`, a module `{-# ANN #-}`, or
`operators.RemoveClause.enable: false`.
* A `SwitchFunctionArguments` mutation operator: at a prefix function (or
constructor) application, for every pair of value arguments that have the
same type, it emits a mutant that swaps those two arguments. Only the
maximal application spine is mutated, and pairs whose arguments have
identical source text are skipped (swapping them would be a no-op).
Symmetric functions make this operator produce equivalent (unkillable)
mutants, so it reads a `skip-calls-to` list of function names from its
per-operator config and skips swapping the arguments of any call to a
listed function:
```yaml
operators:
SwitchFunctionArguments:
skip-calls-to: [max, Data.Set.union]
```
* Mutations now carry an optional `mitigation` hint and the enclosing
`binding` name in the manifest. `SwitchFunctionArguments` sets a mitigation
message pointing at `skip-calls-to`; the binding name lets the run report
print the exact `{-# ANN <binding> ("DisableMutation: <Operator>" ...) #-}`
annotation for each surviving mutation.
## [0.3.0.0] - 2026-06-09
### Added
* Per-operator configuration under the `operators` config object:
```yaml
operators:
ConstEmptyList:
skip-strings: true
Arith:
enable: false
```
Every operator takes an `enable` toggle (`enable: false` disables it, the
same as listing it in `disabled-mutations`). Each operator reads its own
remaining keys, exposed as an opaque `operatorConfigExtra :: Map Text Value`.
The `ConstEmptyList` operator interprets two such keys: `skip-strings`
(do not target `[Char]`/`String` expressions at all — keeps genuine `[a]`,
`a /= Char`, list mutations) and `skip-literal-strings` (skip only
syntactic string literals).
### Changed
* `InstrumentEnv` and `runInstrument` now carry the per-operator
configuration (`Map Text OperatorConfig`) rather than individual flags.
* The `OptParse` settings expose `OperatorConfig`, `operatorsConfigDisabled`,
and `operatorExtraFlag`; the old `OperatorsConfig`/`ConstEmptyListConfig`
types are gone.
## [0.2.1.0] - 2026-06-08
### Fixed
* The instrumenter no longer mutates compiler-generated code. Stock- and
anyclass-derived instance methods (whose `MatchGroup` carries a `Generated`
origin) and the dictionary/evidence `VarBind`s the typechecker materialises
for an instance (e.g. `$dShow`, `$dEnum`) were being instrumented, with
source spans pointing at the `deriving` clause or the instance head. That
produced nonsense diffs like `deriving (Show, (\_ _ -> False), ...)` and
`instance (\_ -> []) where`, and the resulting mutants — living in generated
code rather than the user's own source — could never be killed by a test, so
they surfaced as permanently uncovered mutations. Such bindings are now
skipped.
## [0.2.0.0] - 2026-06-04
### Added
* Three const-function mutation operators that replace an expression of
type `arg1 -> ... -> argN -> T` (with `N >= 0`) by a constant function
returning `T`'s distinguished trivial value: `ConstNothing` for `Maybe a`,
`ConstEmptyList` for `[a]`, and an expanded `ConstBool` for `Bool`
(subsuming the previous arity-0-only `ConstBool` behaviour). At arity 0
the mutant is the bare trivial value; at arity `N >= 1` it is a typed
`(\_ ... _ -> v)` lambda synthesized in the GhcTc AST. Maybe and List
thereby gain coverage that the syntactic-only `MaybeOp` and `ListLit`
miss — calls like `lookup k m` and `concat xs` are now mutated to
`Nothing` and `[]` respectively.
* A coloured human-readable `<Module>.txt` rendering of every manifest is
now written alongside the existing canonical `<Module>.json`. Same
header + unified-diff layout as the runtime's surviving-mutation
report, opening with a `N mutations in M groups` count line so empty
manifests are not mistakeable for rendering accidents.
* `ReplaceOuterSpan RealSrcSpan Text` constructor on `SrcSpanDelta` for
operators that want to replace a wider outer span than the matched
expression's own. Used by the const-family operators when the matched
expression sits at the operator-token position of an infix application
to rewrite the whole `OpApp` source span in prefix form (e.g.
`n < 0` becomes `(\_ _ -> True) (n) (0)`) instead of text-splicing a
lambda into the operator slot and producing nonsense source.
### Changed
* `applyTokenReplace` now collapses multi-line spans correctly (a latent
bug that surfaced once arity-`>=1` operators started emitting multi-line
replacements).
* The walker tracks `HsApp` function-position depth on `InstrumentEnv`,
resetting on the argument side. Const-family operators consult it to
skip arity-`N` firings (`N >= 1`) at sites where the matched expression
is already saturated by `N` enclosing applications, because the
arity-`N` mutant evaluates to what the arity-0 mutation on the
outermost saturated expression already produces.
## [0.1.1.0] - 2026-05-25
### Changed
* Mutations whose desugared form is non-exhaustive (e.g. a `RemoveCase`
that drops the only alternative covering a constructor) are now
auto-killed at instrument time, since the compiler's incomplete-patterns
warning would catch them anyway.
## [0.1.0.0] - 2026-05-22
### Added
* A `DisableMutationsFor <name>` (or `DisableMutationFor <name>`) annotation
whose target names no local binding in the annotated function's body is now
a compile error. Such an annotation disables no mutations, so the plugin
asks for its removal.