EMPL IR
VariableDeclNode
VariableDeclNode
VariableDeclNode represents a variable declaration. It supports both single-name declarations (let x = 1) and destructuring declarations (let { a, b } = obj).
// MPL_Compiler/core/ir/EMPLNode.h:446
class VariableDeclNode : public EMPLNode {
public:
std::string name;
std::shared_ptr<DestructuringPatternNode> pattern;
UIRTypePtr type;
EMPLNodePtr initializer;
bool isConst = false;
bool isMutable = true;
VariableDeclNode() : EMPLNode(NodeKind::VariableDecl) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field reference
| Field | Type | Source | Default | Description |
|---|---|---|---|---|
kind |
NodeKind::VariableDecl |
EMPLNode.h:455 |
— | Inherited discriminator. |
location |
SourceLocation |
EMPLNode.h:75 |
empty | Source position. |
metadata |
unordered_map<string,string> |
EMPLNode.h:76 |
empty | Free-form metadata. |
name |
std::string |
EMPLNode.h:448 |
empty | The single binding name. Required unless pattern is set. Validator V8: must not be set together with pattern. |
pattern |
shared_ptr<DestructuringPatternNode> |
EMPLNode.h:449 |
nullptr |
Destructuring pattern. Either name OR pattern must be set (validator V8). |
type |
UIRTypePtr |
EMPLNode.h:450 |
nullptr |
Type annotation. nullptr means "inferred from initializer". |
initializer |
EMPLNodePtr |
EMPLNode.h:451 |
nullptr |
The right-hand-side expression. MAY be nullptr for forward declarations or parameter-style hoisted bindings. |
isConst |
bool |
EMPLNode.h:452 |
false |
true for compile-time constants and const-style bindings that cannot be reassigned. |
isMutable |
bool |
EMPLNode.h:453 |
true |
false for readonly / immutable bindings. Independent of isConst (some languages distinguish "compile-time constant" from "runtime immutable"). |
Constructors
| Constructor | Source | Notes |
|---|---|---|
VariableDeclNode() (default) |
EMPLNode.h:455 |
All fields default-initialized. |
There is no parameterized constructor.
Methods
| Method | Source | Description |
|---|---|---|
accept(EMPLVisitor& v) override |
EMPLNode.h:457 |
Standard visitor dispatch. |
Source-language mapping
| Source construct | Field configuration |
|---|---|
let x = 5 (JS) |
name="x", initializer=LiteralNode(5), isConst=false, isMutable=true |
const y = 10 (JS) |
name="y", initializer=LiteralNode(10), isConst=true, isMutable=false |
let { a, b } = obj (JS destructure) |
name="", pattern=DestructuringPatternNode{kind=Object, elements=[{a,a}, {b,b}]}, initializer=IdentifierNode("obj") |
let [first, ...rest] = arr (JS destructure) |
name="", pattern=DestructuringPatternNode{kind=Array, elements=[{first}, {rest, isRest=true}]}, initializer=IdentifierNode("arr") |
var x int = 5 (Go) |
name="x", type=UIRType{Int32}, initializer=LiteralNode(5) |
let x: i32 = 5; (Rust) |
name="x", type=UIRType{Int32}, initializer=LiteralNode(5), isConst=false, isMutable=true |
let mut x = 5 (Rust) |
name="x", initializer=LiteralNode(5), isMutable=true |
x: int = 5 (Python type-annotated) |
name="x", type=UIRType{Int32}, initializer=LiteralNode(5) |
final int X = 42; (Java) |
name="X", type=UIRType{Int32}, initializer=LiteralNode(42), isConst=true |
IR construction example (JS frontend)
auto v = std::make_shared<mpl::ir::VariableDeclNode>();
v->name = "greeting";
v->isConst = true;
v->isMutable = false;
v->type = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::String});
auto lit = mpl::ir::LiteralNode::makeString("Hello, world");
v->initializer = lit;
v->location = { .filename = "input.js", .line = 1, .column = 4 };
Validation rules (subset)
- V8: if
nameis non-empty ANDpatternis non-null, validator reports an error. - V8: if both
nameandpatternare empty, validator reports an error. - V8: at least one of
nameorpatternMUST be set.
Backend lower-cases
C++ backend
| Configuration | Lowered form |
|---|---|
isConst=true, value known |
constexpr auto X = …; (or const auto X = …; if non-literal) |
isConst=true, isMutable=false |
const auto X = …; |
isMutable=true (default) |
auto X = …; |
type=UIRType{T} |
T X = …; |
initializer=nullptr |
T X; (zero-initialized; may be T X{}; for safety) |
pattern set |
Emitted as structured-binding or as a sequence of single-name declarations |
Python backend (planned)
| Configuration | Lowered form |
|---|---|
isConst=true |
X: T = value (no Final[] unless set by canonicalizer) |
isMutable=true |
X: T = value |
pattern set |
a, b = obj (or destructuring assignment) |
See also
nodes/literals.md—LiteralNode(used as initializer)nodes/access.md—MemberAccessNode,IndexAccessNode(used in patterns)types.md—UIRType(used astype)
Detailed examples by source language
JavaScript / TypeScript — basic
const x: number = 42;
let y = "hello";
var z; // declared but not initialized
Lowered IR:
// const x: number = 42
auto v1 = std::make_shared<mpl::ir::VariableDeclNode>();
v1->name = "x";
v1->isConst = true;
v1->isMutable = false;
v1->type = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::Float64});
v1->initializer = mpl::ir::LiteralNode::makeNumber(42);
// let y = "hello"
auto v2 = std::make_shared<mpl::ir::VariableDeclNode>();
v2->name = "y";
v2->isConst = false;
v2->isMutable = true;
v2->initializer = mpl::ir::LiteralNode::makeString("hello");
// var z (no initializer)
auto v3 = std::make_shared<mpl::ir::VariableDeclNode>();
v3->name = "z";
v3->isConst = false;
v3->isMutable = true;
v3->initializer = nullptr;
JavaScript — destructuring
const { a, b: alias, ...rest } = obj;
const [first, , third, ...others] = arr;
Lowered IR:
// const { a, b: alias, ...rest } = obj
auto v1 = std::make_shared<mpl::ir::VariableDeclNode>();
v1->isConst = true;
v1->isMutable = false;
v1->pattern = std::make_shared<mpl::ir::DestructuringPatternNode>();
v1->pattern->patternKind = mpl::ir::DestructuringPatternNode::PatternKind::Object;
v1->pattern->elements = {
{ .targetName = "a", .sourceKey = "a", .defaultValue = nullptr, .isRest = false },
{ .targetName = "alias", .sourceKey = "b", .defaultValue = nullptr, .isRest = false },
{ .targetName = "rest", .sourceKey = "..." /* special marker */, .defaultValue = nullptr, .isRest = true },
};
v1->initializer = std::make_shared<mpl::ir::IdentifierNode>("obj");
// const [first, , third, ...others] = arr
auto v2 = std::make_shared<mpl::ir::VariableDeclNode>();
v2->isConst = true;
v2->isMutable = false;
v2->pattern = std::make_shared<mpl::ir::DestructuringPatternNode>();
v2->pattern->patternKind = mpl::ir::DestructuringPatternNode::PatternKind::Array;
v2->pattern->elements = {
{ .targetName = "first", .sourceKey = "0", .defaultValue = nullptr, .isRest = false },
{ .targetName = "", .sourceKey = "1", .defaultValue = nullptr, .isRest = false }, // skipped
{ .targetName = "third", .sourceKey = "2", .defaultValue = nullptr, .isRest = false },
{ .targetName = "others", .sourceKey = "..." /* special marker */, .defaultValue = nullptr, .isRest = true },
};
v2->initializer = std::make_shared<mpl::ir::IdentifierNode>("arr");
Python — type-annotated assignment
x: int = 42
y: list[str] = ["a", "b"]
Lowered IR:
auto v1 = std::make_shared<mpl::ir::VariableDeclNode>();
v1->name = "x";
v1->type = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::Int32});
v1->initializer = mpl::ir::LiteralNode::makeInt(42);
auto v2 = std::make_shared<mpl::ir::VariableDeclNode>();
v2->name = "y";
v2->type = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::List});
v2->type->elementType = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::String});
auto arrLit = std::make_shared<mpl::ir::ArrayLiteralNode>();
arrLit->elements = {
mpl::ir::LiteralNode::makeString("a"),
mpl::ir::LiteralNode::makeString("b"),
};
v2->initializer = arrLit;
Rust — mutability
let x = 5; // immutable
let mut y = 10; // mutable
const Z: i32 = 100; // compile-time constant
Lowered IR:
// let x = 5 → isMutable=false
auto v1 = std::make_shared<mpl::ir::VariableDeclNode>();
v1->name = "x";
v1->isConst = false;
v1->isMutable = false; // Rust immutable binding
v1->initializer = mpl::ir::LiteralNode::makeInt(5);
// let mut y = 10 → isMutable=true
auto v2 = std::make_shared<mpl::ir::VariableDeclNode>();
v2->name = "y";
v2->isConst = false;
v2->isMutable = true;
v2->initializer = mpl::ir::LiteralNode::makeInt(10);
// const Z: i32 = 100 → isConst=true, isMutable=false
auto v3 = std::make_shared<mpl::ir::VariableDeclNode>();
v3->name = "Z";
v3->isConst = true;
v3->isMutable = false;
v3->type = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::Int32});
v3->initializer = mpl::ir::LiteralNode::makeInt(100);
Go
var x int = 42
y := 50 // short declaration, type inferred
Lowered IR:
// var x int = 42
auto v1 = std::make_shared<mpl::ir::VariableDeclNode>();
v1->name = "x";
v1->type = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::Int32});
v1->initializer = mpl::ir::LiteralNode::makeInt(42);
// y := 50 (type inferred)
auto v2 = std::make_shared<mpl::ir::VariableDeclNode>();
v2->name = "y";
v2->type = nullptr; // inferred from initializer
v2->initializer = mpl::ir::LiteralNode::makeInt(50);
C++ backend lowering details
Type inference from initializer
When type is nullptr and initializer is a LiteralNode, the C++ backend infers:
| Literal type | Inferred C++ type |
|---|---|
int64_t |
int64_t (or auto) |
double |
double (or auto) |
string |
std::string (or auto) |
bool |
bool (or auto) |
nullptr |
std::nullptr_t (or auto) |
For complex initializers (function calls, expressions), the backend uses auto and relies on C++ template argument deduction.
Const folding
For:
const PI = 3.14159;
If the C++ backend determines that the initializer is a compile-time constant (no function calls, no non-const globals), it emits constexpr double PI = 3.14159;. Otherwise it falls back to const double PI = 3.14159;.
Destructuring lowering
For:
const { a, b: alias, ...rest } = obj;
The C++ backend emits a structured binding (C++17+) when possible, or a sequence of single-name declarations otherwise:
const auto& _tmp = obj;
const auto& a = _tmp.a;
const auto& alias = _tmp.b;
const auto& rest = [&]() {
auto _r = obj;
_r.erase("a", "b"); // remove the destructured keys
return _r;
}();
Default parameter values
For function parameters with defaults, typedParameters[i].defaultValue holds the expression. The C++ backend emits:
void f(int x, std::string s = "default") { /* … */ }
The default-value expression can be any EMPLNodePtr, including LiteralNode, BinaryOpNode, FunctionCallNode, etc.
Validator invariants
| Rule | Constraint | Source |
|---|---|---|
| V8 | name XOR pattern must be set (exactly one, not both, not neither) |
validator.md |
| V1 | initializer MAY be nullptr (forward declaration) |
validator.md |
| V1 | type MAY be nullptr (inferred) |
validator.md |
Common patterns
Module-level singleton
const config = Object.freeze({ host: "localhost", port: 8080 });
Lowered IR:
auto v = std::make_shared<mpl::ir::VariableDeclNode>();
v->name = "config";
v->isConst = true;
v->isMutable = false;
auto objLit = std::make_shared<mpl::ir::ObjectLiteralNode>();
objLit->properties = {
{ std::make_shared<mpl::ir::LiteralNode>(mpl::ir::LiteralNode::makeString("host")),
std::make_shared<mpl::ir::LiteralNode>(mpl::ir::LiteralNode::makeString("localhost")) },
{ std::make_shared<mpl::ir::LiteralNode>(mpl::ir::LiteralNode::makeString("port")),
std::make_shared<mpl::ir::LiteralNode>(mpl::ir::LiteralNode::makeInt(8080)) }
};
v->initializer = objLit;
v->metadata["frozen"] = "true";
The C++ backend emits inline const auto config = mpl::mpl_make_object({{"host", "localhost"}, {"port", 8080}});.
Tuple destructuring
first, second = (1, 2)
Lowered IR:
auto v = std::make_shared<mpl::ir::VariableDeclNode>();
v->pattern = std::make_shared<mpl::ir::DestructuringPatternNode>();
v->pattern->patternKind = mpl::ir::DestructuringPatternNode::PatternKind::Array;
v->pattern->elements = {
{ .targetName = "first", .sourceKey = "0", .defaultValue = nullptr, .isRest = false },
{ .targetName = "second", .sourceKey = "1", .defaultValue = nullptr, .isRest = false }
};
auto tupleLit = std::make_shared<mpl::ir::TupleLiteralNode>();
tupleLit->elements = {
mpl::ir::LiteralNode::makeInt(1),
mpl::ir::LiteralNode::makeInt(2)
};
v->initializer = tupleLit;
Multiple-assignment from function call
const [a, b, c] = compute();
Lowered IR:
auto v = std::make_shared<mpl::ir::VariableDeclNode>();
v->pattern = std::make_shared<mpl::ir::DestructuringPatternNode>();
v->pattern->patternKind = mpl::ir::DestructuringPatternNode::PatternKind::Array;
v->pattern->elements = {
{ .targetName = "a", .sourceKey = "0", .defaultValue = nullptr, .isRest = false },
{ .targetName = "b", .sourceKey = "1", .defaultValue = nullptr, .isRest = false },
{ .targetName = "c", .sourceKey = "2", .defaultValue = nullptr, .isRest = false }
};
auto call = std::make_shared<mpl::ir::FunctionCallNode>();
call->name = "compute";
v->initializer = call;
The C++ backend emits:
const auto _tmp = compute();
const auto& a = std::get<0>(_tmp);
const auto& b = std::get<1>(_tmp);
const auto& c = std::get<2>(_tmp);
See also (post-summary)
nodes/literals.md—LiteralNodenodes/access.md—MemberAccessNode,IndexAccessNodetypes.md—UIRTypevalidator.md— rule V8