pub enum MirExpr {
Show 23 variants
Literal(Spanned<Literal>),
Local(Spanned<MirLocal>),
Let(Spanned<MirLet>),
Call(Spanned<MirCall>),
TailCall(Spanned<MirTailCall>),
BinOp(Spanned<MirBinOp>),
Neg(Box<Spanned<MirExpr>>),
Match(Spanned<MirMatch>),
Construct(Spanned<MirConstruct>),
RecordCreate(Spanned<MirRecordCreate>),
RecordUpdate(Spanned<MirRecordUpdate>),
Project(Spanned<MirProject>),
IfThenElse(Spanned<MirIfThenElse>),
Try(Box<Spanned<MirExpr>>),
List(Vec<Spanned<MirExpr>>),
Tuple(Vec<Spanned<MirExpr>>),
MapLiteral(Vec<(Spanned<MirExpr>, Spanned<MirExpr>)>),
InterpolatedStr(Vec<MirStrPart>),
IndependentProduct(Spanned<MirIndependentProduct>),
Return(Box<Spanned<MirExpr>>),
FnValue(String),
Box(Box<Spanned<MirExpr>>),
Unbox(Box<Spanned<MirExpr>>),
}Expand description
One MIR expression. Every variant is a value — there’s no
separate statement form at this phase. Sequencing happens via
Let.
Variants§
Literal(Spanned<Literal>)
A literal value (Int, Float, Bool, String, Unit).
Same vocabulary the existing typed AST already uses.
Local(Spanned<MirLocal>)
Read a previously-bound local. The LocalId was introduced
either as a function parameter (MirParam::local) or via a
Let in this body’s lexical scope. Phase 6 wave 4: carries
a last_use flag the lowerer propagates from HIR’s
ResolvedExpr::Resolved { last_use, .. } so backends can
emit MOVE_LOCAL (skipping ref-count bumps) on the final
read of a slot.
Let(Spanned<MirLet>)
let binding = value; body — sequence two expressions and
surface the second’s value. Phase 2a’s only sequencing
primitive; everything else inside a function body composes
from this + the value-form variants below.
Call(Spanned<MirCall>)
Apply a callee (user fn / builtin) to arguments. The callee
kind discriminates so backends know whether to look up via
FnId (typed identity) or via the named-builtin registry.
TailCall(Spanned<MirTailCall>)
Tail call to a user fn — same SCC as the surrounding fn. Backends decide the final shape (wasm-gc tail-call insn, VM tail dispatch, Rust loop rewrite).
BinOp(Spanned<MirBinOp>)
Binary operator over numeric / boolean operands. Same set as
ast::BinOp — MIR doesn’t normalize arithmetic here; that’s
a Phase 6 optimizer concern.
Neg(Box<Spanned<MirExpr>>)
Unary numeric negation. Distinct from BinOp(Sub, 0, x) so
IEEE-754 -0.0 semantics are preserved on Float.
Match(Spanned<MirMatch>)
match <subject> { arm₁ ; arm₂ ; … } — structured. Phase 4
VM walks arms in order, picks the first matching pattern.
Construct(Spanned<MirConstruct>)
Construct a sum-type variant by CtorId. The variant’s
declared fields are filled in argument order.
RecordCreate(Spanned<MirRecordCreate>)
Build a fresh record of a named product type. Fields are
(field_name, value) pairs to keep the dump readable; the
declared field order is determined by TypeId and
validated at lowering time.
RecordUpdate(Spanned<MirRecordUpdate>)
T.update(base, field = v, …) — produce a new record that
matches base except for the named field overrides.
Project(Spanned<MirProject>)
Field access (base.field) on a record value.
IfThenElse(Spanned<MirIfThenElse>)
if cond { then } else { else } — direct conditional
shape introduced by Phase 6 wave 9’s bool_match_to_if
pass. Lowering never produces this node directly; the
optimizer pass rewrites qualifying two-arm Bool match
expressions into it so every backend gets a uniform
if/else node instead of re-implementing the recognition.
Try(Box<Spanned<MirExpr>>)
value? — the canonical ? propagation. Phase 1’s
most-important pin: this stays a node. Lowering to nested
Match is a per-backend choice, not a pipeline-wide
transform. Rust will eventually emit ? native, VM emits
tag-check + early return.
The bound form let x = step()?; body is expressed as
MirExpr::Let { binding: x, value: MirExpr::Try(step()), body } — no dedicated TryBind variant. The original
design had one, but it duplicated the semantics of
Let { value: Try(_), ... } exactly. Consumers that need
to recognize the ?-bind pattern do so by walking
Let and inspecting value.node for MirExpr::Try.
List(Vec<Spanned<MirExpr>>)
[a, b, c] — list literal. Elements lower to MIR
expressions; the resulting value is List<T> with T
inferred at type-check time.
Tuple(Vec<Spanned<MirExpr>>)
(a, b, c) — tuple literal.
MapLiteral(Vec<(Spanned<MirExpr>, Spanned<MirExpr>)>)
{"k" => v, …} — map literal. Keys + values lower as MIR
expressions; the resulting value is Map<K, V>.
InterpolatedStr(Vec<MirStrPart>)
"…{expr}…" — interpolated string. Each part is either a
literal text segment or an embedded MIR expression whose
value gets stringified at runtime.
IndependentProduct(Spanned<MirIndependentProduct>)
Independent product: (a, b, c)! or (a, b, c)?!. The
unwrap_results flag captures the ? form (every element
must be Result<…>; Err short-circuits with the first
error). Schedule (complete / cancel / sequential) is
an aver.toml runtime policy and is NOT carried in MIR.
Return(Box<Spanned<MirExpr>>)
Early return value; — used in lowered bodies that have a
natural early-return shape (the ? propagation lowering
inside a backend is the canonical example). Functions that
don’t return early end their body with the final expression
itself; Return is only for the explicit early-exit case.
FnValue(String)
A fn referenced as a value (not called): callWith(dbl) passes
dbl. Carries the canonical fn / builtin name; the backend
resolves it to a symbol reference (the VM pushes a symbol_ref
constant, mirroring the HIR walker’s StaticRef leaf-op). The
walker falls back to HIR if the name doesn’t resolve (a genuinely
unresolved ident — typecheck-rejected input).
Box(Box<Spanned<MirExpr>>)
Int-representation boundary (ETAP-2): raw i64 → Int. The
inner expression evaluates to a raw machine i64 (a value the Int
“unboxing” analysis proved bare); Box wraps it back into the
arbitrary-precision Int (aver_rt::AverInt) at every escape (a
return-as-Int, a boxed-callee-param arg, an aggregate/record/map
store, a stringify, a boxed Let crossing, a boxed-arithmetic
operand).
Inserted ONLY by the bare_i64::rewrite_for_rust MIR→MIR pass,
which runs LATE (after proof export + shape recognition) on a
per-target clone — so the VM / wasm-gc / proof MIR never contains
this node. Backends that may see it (the Rust codegen) lower it to
aver_rt::AverInt::from_i64(<inner raw>); every other walker treats
it as a transparent pass-through to inner (it never appears on
their path, the arm exists only for exhaustiveness).
Unbox(Box<Spanned<MirExpr>>)
Int-representation boundary (ETAP-2): Int → raw i64. The dual
of MirExpr::Box: the inner expression evaluates to an Int
(aver_rt::AverInt) and Unbox narrows it to a raw machine i64
via the checked to_i64() (the analysis only marks a slot bare when
it provably fits i64, so the narrowing never loses information).
Inserted by the same rewrite; the Rust codegen lowers it to
<inner boxed>.to_i64().expect(...).