
Vary's mutation engine includes 33 operators across two levels and three families (classic, semantic, bytecode). For how these operators fit into contracts, observability, and scoring, see [Advanced overview](/docs/mutation/advanced/). For caching, policy gates, and CLI flags, see [Infrastructure](/docs/mutation/infrastructure/).

## Mutation levels

### Bytecode mutation (default)

`--level bytecode` is the primary mutation mode. It compiles your code once, then patches individual JVM bytecode instructions per mutant. Each mutant loads in an isolated classloader and runs in parallel, so runs finish fast enough for regular development use.

The bytecode runner also supports **changed-only mode**: it hashes each method and only re-tests methods whose hash changed since the last run. Use `--all` to override this and test everything.

### AST mutation

`--level ast` is a secondary mode for deeper analysis. It mutates the parsed syntax tree before compilation. Each mutant gets its own compile pass (constant folding, type checking, bytecode generation). This is slower but gives access to structural mutations that bytecode cannot express, like removing statements, dropping list elements, or swapping function arguments. Use it when investigating specific survivor patterns or when the bytecode operators are not enough.

The AST runner includes a **canary check**: it picks the first relational or logical mutant and verifies it gets killed. If the canary survives, the test harness is broken and the run aborts.

### Both

`--level both` runs AST mutations first, then bytecode mutations, and deduplicates the combined results.

## AST operators

Seventeen classic operators, selected with `--operators` (comma-separated). Use `--operators CLASSIC` to select only these:

### Arithmetic (`arithmetic`)

| Original | Replacement |
|----------|-------------|
| `+` | `-` |
| `-` | `+` |
| `*` | `/` |
| `/` | `*` |
| `%` | `*`, `/` |

### Relational (`relational`)

| Original | Replacements |
|----------|-------------|
| `<` | `<=`, `>`, `==` |
| `<=` | `<`, `>=`, `==` |
| `>` | `>=`, `<`, `==` |
| `>=` | `>`, `<=`, `==` |
| `==` | `!=` |
| `!=` | `==` |

### Logical (`logical`)

| Original | Replacement |
|----------|-------------|
| `and` | `or` |
| `or` | `and` |
| `not expr` | `expr` (negation removed) |

### Literal (`literal`)

| Original | Replacements |
|----------|-------------|
| `0` (Int) | `1`, `-1` |
| `n` (Int, n != 0) | `0`, `n+1`, `n-1`, `-n` (if n > 0) |
| `true` | `false` |
| `false` | `true` |
| `""` (empty string) | `"mutant"` |
| `"text"` (non-empty) | `""` |

### Statement removal (`statement`)

Replaces statements with `pass`. Excluded: `pass`, `break`, `continue`, function/class/interface definitions.

### Boundary (`boundary`)

| Original | Replacement |
|----------|-------------|
| `<` | `<=` |
| `<=` | `<` |
| `>` | `>=` |
| `>=` | `>` |

### Return default (`ret_default`)

Replaces `return expr` with `return 0`, `return ""`, `return False`, or `return None`. Each return statement produces up to 4 mutants.

### Skip effect (`skip_effect`)

Removes side-effecting function calls (calls used as statements, not as values) by replacing them with `pass`.

```vary-snippet
validate(data)  -->  pass
cache.update(key)  -->  pass
```

### Skip block (`skip_block`)

Replaces control flow block bodies with `pass`:

```vary-snippet
if cond { body }     -->  if cond { pass }
for x in items { body }  -->  for x in items { pass }
while cond { body }  -->  while cond { pass }
```

### Drop element (`drop_element`)

Removes one element from a list or dict literal. Only targets literals with 2+ elements.

```vary-snippet
[a, b, c]  -->  [b, c], [a, c], [a, b]
```

### Swap arguments (`swap_args`)

Swaps adjacent arguments with matching types in function calls.

```vary-snippet
f(a, b, c)  -->  f(b, a, c), f(a, c, b)
```

Only swaps pairs where both arguments are the same kind of expression.

### Contract precondition (`contract_precondition`)

Mutates expressions inside `in {}` precondition blocks using the same transforms as arithmetic, relational, and literal operators. A surviving precondition mutant means no test exercises the boundary the precondition defines.

```vary-snippet
def withdraw(amount: Int) -> Int {
    in { amount > 0 }       # mutated to: amount >= 0, amount < 0, etc.
    ...
}
```

### Contract postcondition (`contract_postcondition`)

Mutates expressions inside `out(r) {}` and `post {}` postcondition blocks. A surviving postcondition mutant means the guarantee can be weakened without any test noticing.

```vary-snippet
def abs_val(x: Int) -> Int {
    out (r) { r >= 0 }      # mutated to: r > 0, r <= 0, etc.
    ...
}
```

Contract mutation types are counted separately in the [contract adequacy score](/docs/mutation/contracts/).

### Enum replace (`enum_replace`)

Replaces a nullary enum variant reference with another variant from the same enum. Targets `EnumName.Variant` expressions in value position (comparisons, returns, arguments, assignments). Payload variants are excluded. Only simple variants without parameters are swapped.

```vary-snippet
enum Color { Red, Green, Blue }

def theme() -> Color {
    return Color.Red        # mutated to: Color.Green, Color.Blue
}
```

For a 3-variant enum, each reference generates 2 mutants (one per alternative). A surviving enum mutant means tests do not distinguish between variants.

### Contract remove (`contract_remove`)

Removes an entire contract block (`in {}`, `out(r) {}`, or `post {}`), making the function accept any input or guarantee nothing about its output. A surviving contract-remove mutant means no test exercises the boundary the contract defines.

```vary-snippet
def withdraw(amount: Int) -> Int {
    in { amount > 0 }       # mutated to: (contract removed entirely)
    ...
}
```

### Match swap (`match_swap`)

Swaps the bodies of two match cases. A surviving match-swap mutant means tests do not distinguish between the outcomes of different cases.

```vary-snippet
match status {
    case Status.Active { return 1 }    # body swapped with next case
    case Status.Inactive { return 0 }
}
```

### Match pattern (`match_pattern`)

Mutates match patterns: removes guards, replaces specific patterns with wildcards, or substitutes literal values. A surviving match-pattern mutant means the pattern is not tested precisely enough.

```vary-snippet
match x {
    case n if n > 10 { ... }   # mutated to: case n { ... } (guard removed)
}
```

## Semantic operators

Ten operators that mutate program meaning rather than syntax. Classic mutation catches arithmetic slips and flipped conditions; semantic operators catch the subtler bugs where a developer reads the wrong field, passes arguments in the wrong order, or weakens a null check. Select with `--operators SEMANTIC` or by individual group: `HIGHER_ORDER`, `FIELD`, `TYPE_SEMANTIC`.

### Higher-order operators (`HIGHER_ORDER`)

Compound mutations that affect multiple related expressions at once.

#### Boundary shift (`boundary_shift`)

Shifts a loop bound and its comparison operator together. The classic `boundary` operator changes `<` to `<=` in isolation. Boundary shift adjusts both the comparison and the limit value, which is closer to how real off-by-one bugs happen.

```vary-snippet
for i in range(n) { ... }  -->  for i in range(n - 1) { ... }
while i < len(items) { ... }  -->  while i <= len(items) { ... }
```

#### Guard mismatch (`guard_mismatch`)

Replaces the field checked in a guard condition with a sibling field of the same type. If `order` has both `price: Int` and `qty: Int`, a guard on `order.price` gets swapped to `order.qty`.

```vary-snippet
if order.price > 0 { ... }  -->  if order.qty > 0 { ... }
```

### Field operators (`FIELD`)

Mutations on field access, construction, and data use. These catch the class of bug where you type `self.x` when you meant `self.y`.

#### Field swap (`field_swap`)

Reads a sibling field instead of the intended one. Targets field accesses on data types and classes where another field of the same type exists.

```vary-snippet
let total = item.price * item.qty  -->  let total = item.qty * item.qty
```

#### Omitted read (`omitted_read`)

Removes a field access from a calculation by replacing it with a default value for its type.

```vary-snippet
let margin = revenue - cost  -->  let margin = revenue - 0
```

#### Duplicate field (`duplicate_field`)

Uses one field twice instead of two distinct fields in an expression.

```vary-snippet
let area = width * height  -->  let area = width * width
```

#### Misbound constructor (`misbound_constructor`)

Swaps constructor arguments that have compatible types. If both parameters are `Int`, the values get flipped.

```vary-snippet
Point(x, y)  -->  Point(y, x)
```

### Type-semantic operators (`TYPE_SEMANTIC`)

Mutations that target type-level assumptions: null handling, collection checks, and numeric edge cases.

#### Null weaken (`null_weaken`)

Removes or weakens a null check, allowing `None` to flow where it should not.

```vary-snippet
if x is not None { use(x) }  -->  use(x)
```

#### Null strengthen (`null_strengthen`)

Removes a null-safe fallback, forcing a path that assumes the value is always present.

```vary-snippet
let v = x ?: default_val  -->  let v = x!!
```

#### Collection simplify (`collection_simplify`)

Weakens a collection emptiness or membership check. A `len(items) > 0` guard becomes `True`, letting the empty-list path through.

```vary-snippet
if len(items) > 0 { ... }  -->  if True { ... }
```

#### Numeric boundary (`numeric_boundary`)

Shifts a numeric boundary value or changes division type. Catches off-by-one thresholds and integer-vs-float division bugs.

```vary-snippet
if amount >= 100 { ... }  -->  if amount >= 99 { ... }
x / 3  -->  x // 3
```

### Operator group aliases

Use these aliases with `--operators` to select families of operators:

| Alias | Operators |
|-------|-----------|
| `CLASSIC` | All 6 core operators: arithmetic, relational, logical, literal, statement, boundary |
| `SEMANTIC` | All 5 semantic operators: ret_default, skip_effect, skip_block, drop_element, swap_args |
| `CONTRACT` | All 3 contract operators: contract_precondition, contract_postcondition, contract_remove |
| `MATCH` | Both match operators: match_swap, match_pattern |
| `HIGHER_ORDER` | boundary_shift, guard_mismatch |
| `FIELD` | field_swap, omitted_read, duplicate_field, misbound_constructor |
| `TYPE_SEMANTIC` | null_weaken, null_strengthen, collection_simplify, numeric_boundary |
| `*` | All operators |

## Bytecode operators

Six operators, selected with `--bc-operators` (comma-separated):

### Arithmetic (`arith`)

Replaces JVM arithmetic opcodes across all numeric types (int, long, float, double). `IADD` becomes `ISUB`, `IMUL` becomes `IDIV`, and so on.

### Conditional (`cond`)

Replaces conditional jump instructions: integer comparisons (`IF_ICMPLT` to `IF_ICMPLE`), null checks (`IFNULL` to `IFNONNULL`), and reference equality.

### Return value (`ret`)

Replaces return values with type-appropriate defaults: `0` for int, `0L` for long, `0.0` for float/double, `null` for objects.

### Negation (`neg`)

Removes negation instructions (`INEG`, `LNEG`, `FNEG`, `DNEG`) by replacing them with `NOP`.

### Call skip (`callskip`)

Removes method calls entirely. Pops all arguments and the receiver from the stack, pushes a default return value. Never skips constructors.

### Return poison (`poison`)

Replaces return values with adversarial (non-default) values: `-1` for int, `-1L` for long, `Float.MAX_VALUE`, `Double.MAX_VALUE`, `""` for objects. These values are chosen to trigger failures in callers that assume specific value ranges.

## Bytecode opcode reference

The full opcode-by-opcode mapping for each bytecode operator. The `I*` (32-bit int) and `F*` (32-bit float) variants are rare in compiled Vary code because `Int` compiles to 64-bit `long` and `Float` to 64-bit `double`, but the operators handle them for completeness.

### Arithmetic mutations

| Original | Mutated to |
|---|---|
| `IADD` | `ISUB` |
| `ISUB` | `IADD` |
| `IMUL` | `IDIV` |
| `IDIV` | `IMUL` |
| `IREM` | `IMUL`, `IDIV` |
| `LADD` | `LSUB` |
| `LSUB` | `LADD` |
| `LMUL` | `LDIV` |
| `LDIV` | `LMUL` |
| `LREM` | `LMUL`, `LDIV` |
| `FADD` | `FSUB` |
| `FSUB` | `FADD` |
| `FMUL` | `FDIV` |
| `FDIV` | `FMUL` |
| `FREM` | `FMUL`, `FDIV` |
| `DADD` | `DSUB` |
| `DSUB` | `DADD` |
| `DMUL` | `DDIV` |
| `DDIV` | `DMUL` |
| `DREM` | `DMUL`, `DDIV` |

### Conditional mutations

| Original | Mutated to |
|---|---|
| `IFEQ` (== 0) | `IFNE` |
| `IFNE` (!= 0) | `IFEQ` |
| `IFLT` (< 0) | `IFLE`, `IFGE` |
| `IFLE` (<= 0) | `IFLT`, `IFGT` |
| `IFGT` (> 0) | `IFGE`, `IFLE` |
| `IFGE` (>= 0) | `IFGT`, `IFLT` |
| `IFNULL` | `IFNONNULL` |
| `IFNONNULL` | `IFNULL` |
| `IF_ICMPEQ` | `IF_ICMPNE` |
| `IF_ICMPNE` | `IF_ICMPEQ` |
| `IF_ACMPEQ` | `IF_ACMPNE` |
| `IF_ACMPNE` | `IF_ACMPEQ` |

### Return value defaults

| Return instruction | Pop | Default pushed |
|---|---|---|
| `IRETURN` | `POP` | `ICONST_0` (0) |
| `LRETURN` | `POP2` | `LCONST_0` (0L) |
| `FRETURN` | `POP` | `FCONST_0` (0.0f) |
| `DRETURN` | `POP2` | `DCONST_0` (0.0) |
| `ARETURN` | `POP` | `ACONST_NULL` (null) |

### Return poison values

| Return instruction | Pop | Poison pushed |
|---|---|---|
| `IRETURN` | `POP` | `ICONST_M1` (-1) |
| `LRETURN` | `POP2` | `LDC -1L` |
| `FRETURN` | `POP` | `LDC Float.MAX_VALUE` |
| `DRETURN` | `POP2` | `LDC Double.MAX_VALUE` |
| `ARETURN` | `POP` | `LDC ""` |
