The shape of a selector case subject in the metamutant.
This is the one piece of generated structure that Mutare.Transform writes
and that Mutare.Manifest recognises for Mutare.Poison readback.
Owning it in a single module keeps the producer from hand-building the same AST
literal twice and keeps the consumer from re-deriving how to spot a selector.
A selector case looks like:
case :persistent_term.get(:mutare_active, 0) do
17 -> <mutated> # one clause per mutant id hosted here
18 -> <mutated>
_ -> <original> # the catch-all: baseline + every inactive mutant
endThe subject is one of two shapes. A module-level / :scaffold / head-default
selector reads :persistent_term inline (the form above). A selector inside a
function body instead reads the hoisted active id — a bare reference to the
dispatch variable mutare_active, bound once per function activation (the lifted
dispatcher threads it as the base clause's first parameter; a non-lifted function
binds it in a :do-block prologue), so the per-site :persistent_term.get is gone:
case mutare_active do # the hoisted read — same shape, cheaper subject
17 -> <mutated>
mutare_active -> <original>
endRecognising the subject is enough to find every selector:
subject_ast/0builds the inline subjectTransformsplices in,subject?/2recognises either shape — the inline read unconditionally, the hoisted bare variable only when the active-id variable name is supplied (the predicateMutare.Manifestuses to walk a rendered metamutant, under the name the transform recorded for the file —Mutare.Transform.Result.dispatch_var).
subject?/2 is tolerant of how the subject is parsed back: bare ASTs may keep
:persistent_term/:mutare_active as atoms, while Mutare.Manifest
re-parses with Code.string_to_quoted! plus a literal encoder that wraps literals as
{:__block__, _, [literal]} for Sourceror.get_range/1 compatibility. The
predicate must match both shapes.
Mutare.Selector defines the runtime constants (the :persistent_term key and
the baseline id); this module builds their AST.
Schema builds pass a file namespace to subject_ast/1. Its read projects the
single global selection before it is used by any local selector or lifted guard:
case :persistent_term.get(:mutare_active, 0) do
{:"lib/example.ex", mutare_local_id} when :erlang.is_integer(mutare_local_id) ->
mutare_local_id
0 ->
0
_ ->
:inactive
endThis projection is hoisted with the read in function bodies; default arguments
and other inline positions keep it self-contained. Local gates and exclusions
remain integer-based. :inactive differs from baseline so other files' coverage
gates short-circuit without consulting the tracking flag. subject?/2 recognises
the complete projection, including after literal-encoded reparse. Its inner case
has no positive integer branches and therefore contributes no mutant to Manifest.
Summary
Functions
Returns whether node is a tupled selector subject.
Build the tupled selector subject with one recording point after both inputs
succeed. The clause-local temporaries never enter source clause scopes, and the
original scrutinee's bindings escape unchanged. pattern_subject?/2 reads this
same structure after rendering; coverage gate construction belongs to Recorder.
Returns whether node is a selector subject.
The selector subject Transform splices into every selector/dispatcher case:
:persistent_term.get(<key>, <baseline>), optionally projected into a file namespace.
Functions
Returns whether node is a tupled selector subject.
The tuple-the-scrutinee path starts with {<selector_subject>, <scrutinee>}.
A generated single-clause case records coverage after evaluating this tuple and returns
the tuple unchanged; its temporary bindings stay outside source clause scopes. Recognise
that wrapper only through its coverage record and matching input/output variables.
The mutant clauses gate on the active id in guards.
var is compared against the variable the pattern binds, not against anything inside
the record: Mutare.Coverage.Recorder.record?/1 deliberately ignores the gate, so the
gate may later become a hoisted boolean without breaking attribution. That the gate and
the pattern name the same variable is therefore an emitter invariant now, held by
pattern_subject_ast/5 and its caller building both from one Config.active_var, and
not something this reader can re-check.
Both the bare two-tuple and the {:__block__, _, [{first, scrutinee}]} wrapper
produced by literal-encoded reparse are accepted. var is passed through so the
hoisted selector-variable form is recognised too.
Examples
iex> subject = {Mutare.Metamutant.subject_ast(), {:value, [], nil}}
iex> Mutare.Metamutant.pattern_subject?(subject)
true
iex> subject = {{:mutare_active, [], nil}, {:value, [], nil}}
iex> Mutare.Metamutant.pattern_subject?(subject)
false
iex> Mutare.Metamutant.pattern_subject?(subject, :mutare_active)
true
Build the tupled selector subject with one recording point after both inputs
succeed. The clause-local temporaries never enter source clause scopes, and the
original scrutinee's bindings escape unchanged. pattern_subject?/2 reads this
same structure after rendering; coverage gate construction belongs to Recorder.
active_var must be the same variable the record was gated on — this function is
the one place that pairs them, because the reader no longer can (see
pattern_subject?/2).
Returns whether node is a selector subject.
Mutare.Manifest uses this while walking rendered metamutant source. Three shapes
match:
- the inline read built by
subject_ast/0::persistent_term.get(<key>, <baseline>) - the namespace projection built by
subject_ast/1 - the hoisted read used inside function bodies: a bare reference to the active
mutant variable
var
The hoisted form is recognised only when var is supplied. That prevents an
ordinary source-level case some_var do ... from being treated as a selector.
Literal wrappers added by Mutare.Manifest's readback parse are accepted,
as are bare atoms from ordinary quoted ASTs.
Examples
iex> Mutare.Metamutant.subject?(Mutare.Metamutant.subject_ast())
true
iex> Mutare.Metamutant.subject?({:mutare_active, [], nil})
false
iex> Mutare.Metamutant.subject?({:mutare_active, [], nil}, :mutare_active)
true
The selector subject Transform splices into every selector/dispatcher case:
:persistent_term.get(<key>, <baseline>), optionally projected into a file namespace.
Examples
iex> Mutare.Metamutant.subject_ast() |> Mutare.Metamutant.subject?()
true