The switches that tune the one metamutant compile for speed.
Mutare compiles the metamutant exactly once before any mutant runs. Inference and verification feed diagnostics, so disabling them changes no runtime behavior. The SSA alias optimization does affect generated code; measurements found its compile cost bought no meaningful runtime benefit for the measured AST-rewriting suite, but binary-building loops do benefit. The default keeps the measured compilation saving; see NOTES "Rechecking SSA alias analysis" for the workload-dependent tradeoff. Three switches, one home (this module), three delivery routes:
compiler_env/0— theERL_COMPILER_OPTIONSentryMutare.Runnerapplies to the singlemix compile(the SSA alias pass off).compile_args/1— extramix compileCLI switches for the same invocation (--no-verification, Elixir ≥ 1.19: skips theModule.ParallelCheckerverify pass — undefined-remote warnings and cross-module type checking).project_source/1— wraps project modules declared in the sandbox'smix.exsso their effectiveelixirc_optionsalways disable type-signature inference. This survives Mix's project cache and applies on every sandbox boot, including umbrella children and projects with a custom config path. It reports whether the wrapper was applied and, when it was not, why. Some source forms cannot be wrapped; those projects compile with inference on.
seed_manifest/1 reconciles the inference option in a transplanted Elixir
compile manifest with that wrapper. Without it, Elixir 1.18/1.19 detects changed
elixirc_options and discards every seeded app beam on the first compile. It
must run only for an app project_source/1 reported wrapping: applied to one
compiling with inference on, it manufactures the very mismatch it exists to
prevent, and the seed reports reuse for a build Mix discards.
The last two exist because Elixir's type checker (≥ 1.18/1.19) is pathological on metamutant-shaped code: measured on phoenix_live_view (~23k sites), inference + verification take a 14 s compile past 80 minutes; with both off it is 14 s again. See NOTES "Type inference and verification on the metamutant compile".
A per-mutant mix test never recompiles the lib (sources unchanged), so the
env and args carry nothing there. The project option remains the same on later
baseline/probe boots, preventing inference configuration changes from making
Mix recheck the metamutant.
Summary
Functions
Extra mix compile CLI switches for the metamutant compile.
Env entries that tune the one metamutant compile for speed: an
ERL_COMPILER_OPTIONS disabling the SSA alias-analysis pass
(no_ssa_opt_alias).
The name of the env var compiler_env/0 sets (ERL_COMPILER_OPTIONS).
Listed in Mutare.Sandbox.Command.Invocation.reserved_env_names/0, since the
compile appends the partition entry after this one.
Build the ERL_COMPILER_OPTIONS value for the metamutant compile: prepend
no_ssa_opt_alias to any inherited value (an Erlang term-list string,
or nil/"" for none), always returning a well-formed [...] list string. Pure,
so the merge is unit-testable.
Override inference in the sandbox copy of a Mix project's effective options.
Align a freshly seeded Elixir manifest with the sandbox inference override.
Functions
Extra mix compile CLI switches for the metamutant compile.
--no-verification (Elixir ≥ 1.19.0) skips the
Module.ParallelChecker verify pass — undefined-remote-function warnings and
cross-module type checking. Warnings on a build artifact are noise, and the
type checker is pathological on metamutant-shaped code (measured: ~12 minutes
on phoenix_live_view with inference already off). Poison detection is
unaffected: it reads hard compile errors, which are raised during
compilation, not verification.
version defaults to the running Elixir; it is a parameter so the gate is
unit-testable.
Env entries that tune the one metamutant compile for speed: an
ERL_COMPILER_OPTIONS disabling the SSA alias-analysis pass
(no_ssa_opt_alias).
Read by Mutare.Runner for the single mix compile. Scoped there on purpose: a
per-mutant mix test never recompiles the lib (sources unchanged), so it carries
nothing. Merges with any ERL_COMPILER_OPTIONS already in the environment so a
user's own compiler options survive — ours is prepended (erl_compiler_options/1).
@spec env_var() :: String.t()
The name of the env var compiler_env/0 sets (ERL_COMPILER_OPTIONS).
Listed in Mutare.Sandbox.Command.Invocation.reserved_env_names/0, since the
compile appends the partition entry after this one.
Build the ERL_COMPILER_OPTIONS value for the metamutant compile: prepend
no_ssa_opt_alias to any inherited value (an Erlang term-list string,
or nil/"" for none), always returning a well-formed [...] list string. Pure,
so the merge is unit-testable.
Override inference in the sandbox copy of a Mix project's effective options.
Returns {:hooked, source} when a hook was attached to a module defined in this file,
and {:declined, source, reason} otherwise, reason briefly describing why no hook was attached.
The caller needs both. Mutare.Sandbox.Seed may only realign the compile manifest of an
app that really will compile with inference off, and the source alone does not establish that:
a file that defines no module of its own still comes back changed (the bootstrap is
prepended unconditionally), so != is not a proxy for it. And a declined project compiles
with inference on, which on metamutant-shaped code can stretch the one compile from
seconds to hours, so the reason is included in sandbox diagnostics.
A before_compile hook wraps project/0, preserving its computed configuration
and every other compiler option. Only sandbox project files are rewritten;
the target's original options remain untouched. Unsupported Elixir versions
retain the original configuration. Quoted module definitions are left alone.
The function returns {:declined, source, reason} with the original source byte-for-byte when:
- the source does not parse;
- the rewritten file does not parse, or Elixir reads it back as anything other than
the bootstrap followed by the original with each module body hooked.
The two are compared as parsed programs, ignoring only spellings that evaluate
alike (metadata, block nesting, an empty block as
nil, a charlist as a~csigil), so a render that still parses but moved an expression, altered a literal, or lost a hook is caught instead of becoming the sandbox's entry point; - the rewrite raises, throws, or exits.
Parsing and rendering are best-effort, so an unused umbrella child or a scaffolding
template cannot abort sandbox preparation. Keeping the original merely leaves inference
on, where a mix.exs rendered into a different program would break the sandbox or
change what it builds.
For a mix.exs defining no module of its own, the result is also :declined, but includes rewritten source (the
bootstrap is inert there): a project module built entirely in an externally required
file is outside this source rewrite, and its compiler options must currently disable
inference themselves.
:hooked reports the source rewrite, which is one step short of certainty: the hook
itself checks at compile time that the module is a Mix project defining project/0, so
a mix.exs holding only an unrelated helper module alongside a required-in project
reports :hooked and is nonetheless declined. Nothing observable before the compile can
close that gap, and the manifest is stamped before it.
Merely setting Code.put_compiler_option(:infer_signatures, false) before
compilation is insufficient: Elixir 1.20 Mix unconditionally derives inference
from elixirc_options, defaulting to true. Updating the current project stack
also fails when Mix pushes a cached umbrella project again. Wrapping the
project's return value covers both cases without interpreting its build code.
Align a freshly seeded Elixir manifest with the sandbox inference override.
Only the inference entry of the compiler cache key changes. Existing beams remain valid with inference disabled; all other options, source paths and dependency/configuration records retain their ordinary invalidation behavior. This must only run when transplanting the target's build, never on a retained sandbox build or a dependency build.
Mix's manifest is private: recognize the 1.18/1.19 layouts (versions 26–29), and leave unknown layouts alone, allowing Mix to cold-compile if necessary. Elixir 1.20 removes inference from this key and needs no adjustment here.