Mutare.Mutators.ReturnValue (mutare v0.1.1)

Copy Markdown View Source

Replaces function return expressions with fixed constants.

It applies to each def and defp return path. When a clause tail is a case/cond/if/unless/with/try/receive, each branch's tail is its own return path. The value of a try after block is excluded because it is discarded. Each clause of an anonymous function is handled in the same way.

Each eligible return path produces two replacements based on its shape:

tail shapeempty/zerosentinel
numeric (a + b, x * 2, div(a, b), -n)01
string concatenation (a <> b)"""mutare"
list expression (a ++ b, xs -- ys)[][:mutare]
anything else (variable, call, tuple, map,nil:mutare
:ok/:error atom, an opaque-macro result)

A replacement equal to the original return is omitted.

Two separate rules keep this family from restating another's work, and they answer different questions. A literal tail belongs to its node-level family outright, whatever the run enables — see Exclusions. On every tail this family does claim, the transform then compares values: when an enabled node-level family already produces the same scalar replacement at that return expression, it keeps the node-level mutation and drops this family's duplicate. So with AtomLiteral enabled, :foo → :mutare is reported as atom while :foo → nil stays a return_value mutation; disable AtomLiteral and both replacements come back.

Exclusions

  • Boolean expressions are handled by Mutare.Mutators.Conditional.
  • Integer, float, string, list, and boolean literals are handed to their node-level families outright — unlike the value comparison above, this holds even when that family is disabled. On a literal tail the contrasting pair earns nothing: nil/:mutare collide with no replacement those families emit, so no comparison would catch them, and they are at best weaker restatements of one. A do: false tail mutated to nil is the clearest case — both are falsy, so every refute-style assertion lets it through, and only a strict comparison or a false pattern match kills it. Bare atoms remain eligible.
  • nil return expressions are not mutated.
  • A unit-returning function — one whose every return path, across all its clauses, is literally :ok or nil — has no return positions at all. Its return carries no data, so a replacement there survives whenever the caller discards the value (the norm for a side-effect helper) and is killed only by a test asserting a static fact. A behaviour callback is exempt — a function that is a callback of one of the module's declared behaviours, or that carries an @impl — because its caller is the behaviour's runtime, which may inspect a lone :ok as one contract outcome among several (an Oban.Worker.perform/1). Anonymous functions are classified the same way. The classification is syntactic, over the clauses visible in the source (a clause a macro generates beside hand-written ones of the same signature is invisible to it), and otherwise errs only toward missing a unit function: a call in tail position (Logger.info(x)), a variable bound to :ok, or an else-less with (its non-matching value is a return path) is not recognised, and one :ok path beside a {:error, _} path is still mutated — that :ok carries the success bit. A tail that never returns (raise/reraise/throw/exit, or the :erlang primitives) is not a return path, so an :ok-or-raise function is unit; the raising tail itself keeps its return mutants.
  • quote blocks are compile-time code and are not mutated as a whole.

The empty and sentinel variants can be selected independently in an ignore directive, for example # mutare:ignore[return_value:empty]. This family is enabled by default.

Summary

Functions

Returns clean-meta constant replacements for a clause return expression.

Functions

return_replacements(tail)

@spec return_replacements(Macro.t()) :: [Macro.t()]

Returns clean-meta constant replacements for a clause return expression.

Eligible expressions receive an empty or zero value and a non-empty sentinel, excluding any replacement equal to the original. Returns [] for expressions handled by another value family, boolean expressions, nil, and quoted code.