The fluent builder API provides programmatic MIR construction. Prefer the body! macro for most cases - use this API only when the macro doesn't support a required feature.
Source: libs/@local/hashql/mir/src/builder/
use hashql_core::{heap::Heap, r#type::{TypeBuilder, environment::Environment}};
use hashql_mir::{builder::BodyBuilder, intern::Interner};
let heap = Heap::new();
let interner = Interner::new(&heap);
let builder = BodyBuilder::new(&interner);
let env = Environment::new(&heap);Creates operators for binary/unary operations:
use hashql_mir::builder::op;
// Binary: ==, !=, <, <=, >, >=, &, |, +, -, *, /
rv.binary(x, op![==], y)
// Unary: !, neg
rv.unary(op![!], cond)
rv.unary(op![neg], value)let env = Environment::new(&heap);
// Common types
let int_ty = TypeBuilder::synthetic(&env).integer();
let bool_ty = TypeBuilder::synthetic(&env).boolean();
let null_ty = TypeBuilder::synthetic(&env).null();
// Declare locals
let x = builder.local("x", int_ty); // Returns Place<'heap>let const_42 = builder.const_int(42);
let const_true = builder.const_bool(true);
let const_unit = builder.const_unit();
let const_null = builder.const_null();
let const_fn = builder.const_fn(def_id);// Reserve without parameters
let bb0 = builder.reserve_block([]);
// Reserve with block parameters (for SSA phi-like merging)
let bb1 = builder.reserve_block([x.local, y.local]);builder
.build_block(bb0)
.assign_place(x, |rv| rv.load(const_1))
.assign_place(y, |rv| rv.binary(x, op![==], x))
.storage_live(local)
.storage_dead(local)
.nop()
.ret(result); // Must end with terminator// Return
builder.build_block(bb).ret(value);
// Goto
builder.build_block(bb0).goto(bb1, []);
builder.build_block(bb0).goto(bb1, [x.into(), y.into()]);
// If-else
builder.build_block(bb0).if_else(cond, bb_then, [], bb_else, []);
// Switch
builder.build_block(bb0).switch(selector, |switch| {
switch.case(0, bb1, []).case(1, bb2, []).otherwise(bb3, [])
});
// Unreachable
builder.build_block(bb).unreachable();| Method | Creates | Example |
|---|---|---|
load(operand) |
Copy/move | rv.load(x) |
binary(l, op, r) |
Binary op | rv.binary(x, op![+], y) |
unary(op, val) |
Unary op | rv.unary(op![!], cond) |
tuple([...]) |
Tuple | rv.tuple([x, y, z]) |
list([...]) |
List | rv.list([a, b, c]) |
struct([...]) |
Struct | rv.r#struct([("x", val)]) |
closure(def, env) |
Closure | rv.closure(def_id, env_place) |
dict([...]) |
Dict | rv.dict([(k, v)]) |
apply(fn, args) |
Call | rv.apply(func, [arg1]) |
call(fn) |
Call (no args) | rv.call(func) |
input(op, name) |
Input | rv.input(InputOp::Load { required: true }, "x") |
let tup = builder.local("tup", tuple_ty);
let tup_field_0 = builder.place(|place| place.from(tup).field(0, int_ty));
// Nested projections
let nested = builder.place(|place| {
place.from(outer).field(0, inner_ty).field(1, int_ty)
});use hashql_core::{heap::Heap, r#type::{TypeBuilder, environment::Environment}};
use hashql_mir::{builder::BodyBuilder, intern::Interner, op};
let heap = Heap::new();
let interner = Interner::new(&heap);
let builder = BodyBuilder::new(&interner);
let env = Environment::new(&heap);
let int_ty = TypeBuilder::synthetic(&env).integer();
let x = builder.local("x", int_ty);
let const_1 = builder.const_int(1);
let bb0 = builder.reserve_block([]);
builder
.build_block(bb0)
.assign_place(x, |rv| rv.load(const_1))
.ret(x);
let body = builder.finish(0, int_ty);Use the fluent builder API when the body! macro doesn't support your use case:
GraphReadterminator- Index projections (e.g.,
list[idx]) - Complex dynamic DefId manipulation
- Other advanced MIR constructs not yet in the macro
For all other cases, prefer the body! macro for clarity and maintainability.