Changelog of @hackage/sydtest-mutation-plugin 0.4.4.0

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:

    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:

    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 VarBinds 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.