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, recovering that name once viaManifest.active_var/1).
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 has to see through both shapes.
Mutare.Selector owns the runtime constants (the :persistent_term key and
the baseline id); this module owns their AST.
Summary
Functions
Returns whether node is a tupled selector subject.
Returns whether node is a selector subject.
The selector subject Transform splices into every selector/dispatcher case:
:persistent_term.get(<key>, <baseline>).
Functions
Returns whether node is a tupled selector subject.
The tuple-the-scrutinee path emits case subjects as
{<selector_subject>, <scrutinee>}. The mutant clauses then gate on the active
id in guards, so Mutare.Manifest recognises the dispatch by checking the
tuple's first element with subject?/2.
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
Returns whether node is a selector subject.
Mutare.Manifest uses this while walking rendered metamutant source. Two shapes
match:
- the inline read built by
subject_ast/0::persistent_term.get(<key>, <baseline>) - 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
@spec subject_ast() :: Macro.t()
The selector subject Transform splices into every selector/dispatcher case:
:persistent_term.get(<key>, <baseline>).
Examples
iex> Mutare.Metamutant.subject_ast() |> Mutare.Metamutant.subject?()
true