Mutare.Mutator.Structural behaviour (mutare v0.1.2)

Copy Markdown View Source

Behaviour for mutating positions that are larger than a single AST node: clause return values, if/unless/cond conditions, and structural pattern positions.

Declare both Mutare.Mutator and this behaviour, define name/0, then implement whichever structural callbacks you need. A structural mutator does not need Mutare.Mutator.mutate/1.

defmodule MyApp.Mutators.AlwaysReturnNil do
  @behaviour Mutare.Mutator
  @behaviour Mutare.Mutator.Structural

  @impl Mutare.Mutator
  def name, do: :always_nil

  @impl Mutare.Mutator.Structural
  def return_replacements(_tail), do: [Mutare.AST.literal(nil)]
end

Each callback also has a context-taking arity for reading the enabled mutator's options and restricting mutations by the enclosing module's @behaviour set. Implement either the base arity or its context-taking counterpart.

Summary

Types

Context passed to the context-aware structural callbacks (return_replacements/2, condition_replacements/2, pattern_mutations/3). Carries

Callbacks

Returns replacements for an if, unless, or cond condition.

Context-aware form of condition_replacements/1.

Returns pattern replacements for a structural pattern position.

Context-aware form of pattern_mutations/2.

Returns replacements for a clause return expression.

Context-aware form of return_replacements/1.

Types

context()

@type context() :: %{opts: term(), config: term(), behaviours: MapSet.t(module())}

Context passed to the context-aware structural callbacks (return_replacements/2, condition_replacements/2, pattern_mutations/3). Carries:

  • :opts — the options from a {Module, opts} mutator configuration entry, or [] for an unconfigured mutator;
  • :config — the normalized configuration Mutare.Mutator.init/1 returned for those options (the options themselves when the mutator has no init/1);
  • :behaviours — the enclosing module's @behaviour set, as a MapSet.

This gives structural mutators the same configuration channel as Mutare.Mutator.mutate/2, without adding context to the base callback arities.

Callbacks

condition_replacements(condition)

(optional)
@callback condition_replacements(condition :: Macro.t()) :: [Macro.t()]

Returns replacements for an if, unless, or cond condition.

Return [] when the condition is not eligible.

condition_replacements(condition, context)

(optional)
@callback condition_replacements(condition :: Macro.t(), context :: context()) :: [
  Macro.t()
]

Context-aware form of condition_replacements/1.

Implement this form to use configuration or the enclosing module's behaviours. When exported, it takes precedence over condition_replacements/1.

pattern_mutations(head_args, used_outside)

(optional)
@callback pattern_mutations(head_args :: [Macro.t()], used_outside :: MapSet.t()) :: [
  [Macro.t()]
]

Returns pattern replacements for a structural pattern position.

head_args contains the patterns for that position. For def/defp heads and multi-pattern clauses, this is the whole pattern list; for single-pattern positions such as a destructuring match, a case clause, or a routed :binding_pattern macro argument, this is a one-element list. used_outside contains variable names read after the pattern. Each returned list must be a valid, compile-safe replacement for the same pattern position.

pattern_mutations(head_args, used_outside, context)

(optional)
@callback pattern_mutations(
  head_args :: [Macro.t()],
  used_outside :: MapSet.t(),
  context :: context()
) :: [[Macro.t()]]

Context-aware form of pattern_mutations/2.

Implement this form to use configuration or the enclosing module's behaviours. When exported, it takes precedence over pattern_mutations/2.

return_replacements(tail)

(optional)
@callback return_replacements(tail :: Macro.t()) :: [Macro.t()]

Returns replacements for a clause return expression.

Each result must be clean-meta AST suitable for direct insertion. Return [] when the expression is not eligible.

The tails of a unit-returning function — every return path of every clause literally :ok or nil — are never offered to this callback; see the exclusions in Mutare.Mutators.ReturnValue.

return_replacements(tail, context)

(optional)
@callback return_replacements(tail :: Macro.t(), context :: context()) :: [Macro.t()]

Context-aware form of return_replacements/1.

Implement this form to use configuration or the enclosing module's behaviours. When exported, it takes precedence over return_replacements/1.