Suppresses selected mutants with a source comment. Ignored mutants remain in the report but are excluded from the mutation score. Their generated code is omitted from the metamutant, reducing compiler input and preventing their replacements from poisoning the build. They retain their IDs, reasons, and positions within :max_mutants.
A trailing directive applies to its own line. A standalone directive applies to the next line of code, reading through any comment lines in between — so it works at either end of an explanatory comment block:
expression() # mutare:ignore
# mutare:ignore
expression()
# mutare:ignore[integer] checked by the boundary test;
# any non-empty replacement is behaviorally equivalent
expression()A blank line ends the comment block: the directive then targets the blank line and suppresses nothing (warned — see below).
A plain # mutare:ignore covers exactly one line of code (see "Scoped directives" below for regions and whole files). For a multi-line expression, place it directly above the line that carries the mutated code — above the |> step containing it, not the pipe's first line:
digested_files
# mutare:ignore[atom] delivery order is discarded
|> Task.async_stream(&write/1, ordered: false)
|> Stream.run()A directive misplaced onto an earlier line of the same expression suppresses nothing; the resulting warning points at the line that has the matching mutants.
A directive may include a family filter, a reason, or both:
# mutare:ignore suppress every mutant on the line
# mutare:ignore equivalent under int math suppress all; record the reason
# mutare:ignore[arithmetic] suppress only arithmetic mutants
# mutare:ignore[arithmetic, relational] suppress two families
# mutare:ignore[relational:>] suppress only the `i > j` swap
# mutare:ignore[integer] off-by-one is fine a filter and a reason togetherFilter entries are mutator family names from Mutare.Mutators.families/0 or a custom mutator's Mutare.Mutator.name/0. Without a filter, the directive suppresses every mutant on the line.
Scoped directives: regions and whole files
Annotating a large low-value span line by line — a literal lookup table, a generated module — doesn't scale, so two scoped verbs suppress in bulk. Both take the same optional [...] filter and reason as # mutare:ignore:
# mutare:ignore-start spot-checked; exhaustively covered by the round-trip test
def encode(?A), do: ?B
def encode(?B), do: ?C
def encode(?C), do: ?D
# mutare:ignore-endAn # mutare:ignore-start opens a region and the next # mutare:ignore-end closes it; every mutant on the delimiter comments' lines and between them is suppressed (the span is inclusive of both delimiter lines, so a trailing -start or -end on a code line covers that line's mutants too). Text after # mutare:ignore-end is plain prose — the filter and reason belong on the -start.
# mutare:ignore-file, anywhere in a file (conventionally at the top), suppresses every matching mutant in that file:
# mutare:ignore-file generated by `mix gen.unicode_tables` — do not hand-editA broken region pairing is a hard Mutare.Ignore.SpecError, not a warning: an -end with no open -start, a second -start inside an open region (regions don't nest), or a -start never closed each abort the run — the mistake is provable, and both lenient readings fail the wrong way (silently suppressing to end-of-file, or silently suppressing nothing).
When several directives match one mutant, the most specific filter wins ([family:label] over [family] over unfiltered); on a tie, the narrowest scope (line over region over file), so the recorded reason is the most locally-written one. Scoped directives are held to the same ineffectiveness bar as line directives: an -file or region that suppresses nothing is warned (and aborts under --strict-ignores).
Use family:label to select one variant from a family. For example, [relational:<=] suppresses the <= replacement but not the other relational replacements. Labels are declared by each family and matched case-insensitively. If a mutant has several labels, any matching label suppresses it. Run mix mutare --list-mutators for the complete built-in list.
Text after the keyword or filter is stored as the ignore reason and shown in the report.
A recognizable equivalent mutant: the re-stated delegate guard
A pattern worth knowing when triaging guard_drop/pattern_guard survivors in wrapper-heavy code — a thin wrapper that re-states the guard of the function it delegates to:
def sign(data, salt) when is_binary(salt),
do: Plug.Crypto.sign(data, salt) # Plug.Crypto.sign/2 has the identical guardDropping the wrapper's guard is provably unobservable: a bad input still raises the same FunctionClauseError, one stack frame deeper, inside the delegate. Verify against the delegate's source (the guards must really be equivalent), then ignore with a reason naming it:
# mutare:ignore[guard_drop] Plug.Crypto.sign/2 re-checks is_binary(salt)
def sign(data, salt) when is_binary(salt), do: Plug.Crypto.sign(data, salt)This shape recurs constantly in real codebases (any module wrapping a well-guarded library); recognizing it saves convincing yourself a survivor is "just uncovered" when it is actually unreachable.
When a directive errors or does nothing
An unknown label for a built-in family, including a disabled one, or an active custom family is an error. Unknown families, bare-family typos, empty filters, and malformed filters match nothing. Any directive that suppresses no mutant produces a warning; --strict-ignores turns that warning into a non-zero exit.
The mutare: comment namespace is reserved. A comment that starts with # mutare: but does not spell a recognized directive — a typo (# mutare:ingore), a stray space after the colon (# mutare: ignore), or a directive from a future Mutare version — is never silently inert: it produces the same scan-time warning, and --strict-ignores the same non-zero exit.
Only source comments are parsed. Text such as "# mutare:ignore" inside a string has no effect.