packages feed

sydtest-mutation-plugin-0.4.4.0: CHANGELOG.md

# Changelog

## [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.