EMPL IR
ClassDeclNode
ClassDeclNode
ClassDeclNode represents a class declaration. The same node is reused for struct declarations (signalled by isStruct = true) and for trait / mixin declarations in some source languages.
// MPL_Compiler/core/ir/EMPLNode.h:183
class ClassDeclNode : public EMPLNode {
public:
std::string name;
bool isStruct = false;
EMPLNodePtr baseClass;
std::shared_ptr<FunctionDeclNode> constructor;
std::vector<std::shared_ptr<FunctionDeclNode>> methods;
std::vector<EMPLNodePtr> members;
std::vector<EMPLNodePtr> staticBlocks;
std::vector<EMPLNodePtr> decorators;
ClassDeclNode() : EMPLNode(NodeKind::ClassDecl) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field reference
| Field | Type | Source | Default | Description |
|---|---|---|---|---|
kind |
NodeKind::ClassDecl |
EMPLNode.h:194 |
— | 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:185 |
empty | Class identifier. Required (validator rule V9). |
isStruct |
bool |
EMPLNode.h:186 |
false |
true for value-type / struct-style declarations (TS interface, C# struct, Rust struct). Affects how the C++ backend lays out members (POD vs class with vtable). |
baseClass |
EMPLNodePtr |
EMPLNode.h:187 |
nullptr |
Base class reference. Must be either an IdentifierNode or a MemberAccessNode (validator rule V9). nullptr means no base (other than Object). |
constructor |
shared_ptr<FunctionDeclNode> |
EMPLNode.h:188 |
nullptr |
The constructor. nullptr if the class has no explicit constructor. |
methods |
vector<shared_ptr<FunctionDeclNode>> |
EMPLNode.h:189 |
empty | Method declarations (instance + static). The isStatic flag on each FunctionDeclNode discriminates. |
members |
vector<EMPLNodePtr> |
EMPLNode.h:190 |
empty | Field declarations (each typically a VariableDeclNode) and property declarations. May also contain index signatures, computed properties, etc. |
staticBlocks |
vector<EMPLNodePtr> |
EMPLNode.h:191 |
empty | Static initializer blocks (JS static { … }, Java static { … }, C# static { … }). Each is a BlockNode. |
decorators |
vector<EMPLNodePtr> |
EMPLNode.h:192 |
empty | Decorator / attribute nodes (TS @decorator, Python @decorator, C# [Attribute], Java @Annotation). Typically FunctionCallNode or IdentifierNode. |
Constructors
| Constructor | Source | Notes |
|---|---|---|
ClassDeclNode() (default) |
EMPLNode.h:194 |
All fields default-initialized. |
There is no parameterized constructor.
Methods
| Method | Source | Description |
|---|---|---|
accept(EMPLVisitor& v) override |
EMPLNode.h:196 |
Standard visitor dispatch. |
Source-language mapping
| Source construct | Field configuration |
|---|---|
class A {} (JS / Java / C#) |
name="A", isStruct=false, baseClass=null, constructor=null, methods=[], members=[] |
class B extends A {} |
name="B", baseClass=IdentifierNode("A") |
struct Point { x: number; y: number } (TS) |
name="Point", isStruct=true, members=[VariableDecl(x), VariableDecl(y)] |
class C { static count = 0; constructor() {} foo() {} } |
name="C", constructor=FunctionDecl("constructor"), methods=[FunctionDecl("foo")], members=[VariableDecl("count", isStatic via metadata)] |
@Component class C {} (TS decorator) |
name="C", decorators=[IdentifierNode("Component")] |
data class D(val x: Int) (Kotlin) |
name="D", members=[VariableDecl("x")], constructor=FunctionDecl(…) |
IR construction example (JS frontend)
auto cls = std::make_shared<mpl::ir::ClassDeclNode>();
cls->name = "Point";
cls->isStruct = false;
cls->baseClass = nullptr;
auto xField = std::make_shared<mpl::ir::VariableDeclNode>();
xField->name = "x";
xField->isConst = false;
xField->isMutable = true;
cls->members.push_back(xField);
auto yField = std::make_shared<mpl::ir::VariableDeclNode>();
yField->name = "y";
cls->members.push_back(yField);
auto ctor = std::make_shared<mpl::ir::FunctionDeclNode>();
ctor->name = "constructor";
ctor->isConstructor = true;
ctor->parameters = { {"x", "x"}, {"y", "y"} };
// body: this.x = x; this.y = y
cls->constructor = ctor;
cls->location = { .filename = "input.js", .line = 1, .column = 0 };
Validation rules (subset)
- V9:
namemust be non-empty. - V9: if
baseClassis non-null, itskindmust beIdentifierorMemberAccess. - V9: any abstract method (
body == nullptr) must haveisStatic = false.
Backend lower-cases
C++ backend
For isStruct = false:
class Point {
public:
int64_t x;
int64_t y;
Point(int64_t x, int64_t y) : x(x), y(y) {}
// …methods…
};
For isStruct = true:
struct Point {
int64_t x;
int64_t y;
};
decorators are lowered by emitting a C++ attribute (C++23 [[…]]) or by attaching the decorator as a static call before the class definition (for legacy targets). The exact emission is controlled by MPL_DECORATOR_STYLE (env var).
staticBlocks are emitted as anonymous-namespace lambdas called immediately after the class definition.
See also
nodes/function-decl.md—FunctionDeclNode(used inmethods,constructor)nodes/variable-decl.md—VariableDeclNode(used inmembers)types.md—UIRType(used in field types)
Detailed examples by source language
JavaScript ES2022
class Point {
#x;
#y;
static origin = new Point(0, 0);
constructor(x, y) {
this.#x = x;
this.#y = y;
}
get distance() { return Math.sqrt(this.#x ** 2 + this.#y ** 2); }
static create(x, y) { return new Point(x, y); }
}
Lowered IR (abbreviated):
auto cls = std::make_shared<mpl::ir::ClassDeclNode>();
cls->name = "Point";
cls->isStruct = false;
// Private fields (#x, #y)
auto xField = std::make_shared<mpl::ir::VariableDeclNode>();
xField->name = "#x";
xField->isMutable = true;
xField->isConst = false;
cls->members.push_back(xField);
auto yField = std::make_shared<mpl::ir::VariableDeclNode>();
yField->name = "#y";
yField->isMutable = true;
cls->members.push_back(yField);
// Static field
auto originField = std::make_shared<mpl::ir::VariableDeclNode>();
originField->name = "origin";
originField->isMutable = false;
originField->isConst = false;
// initializer = FunctionCallNode("Point", Literal(0), Literal(0))
cls->members.push_back(originField);
// Constructor
auto ctor = std::make_shared<mpl::ir::FunctionDeclNode>();
ctor->name = "constructor";
ctor->isConstructor = true;
ctor->parameters = { {"x", "x"}, {"y", "y"} };
// body: this.#x = x; this.#y = y
cls->constructor = ctor;
// Getter (instance method, no body marker for getter)
auto distanceGetter = std::make_shared<mpl::ir::FunctionDeclNode>();
distanceGetter->name = "distance";
distanceGetter->metadata["is_getter"] = "true";
// body: Math.sqrt(this.#x ** 2 + this.#y ** 2)
cls->methods.push_back(distanceGetter);
// Static method
auto createStatic = std::make_shared<mpl::ir::FunctionDeclNode>();
createStatic->name = "create";
createStatic->isStatic = true;
createStatic->parameters = { {"x", "x"}, {"y", "y"} };
// body: return new Point(x, y)
cls->methods.push_back(createStatic);
Python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@staticmethod
def origin():
return Point(0, 0)
@property
def distance(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
Lowered IR:
auto cls = std::make_shared<mpl::ir::ClassDeclNode>();
cls->name = "Point";
cls->sourceLanguage = "python"; // canonicalized by canonicalizer rule C5
auto ctor = std::make_shared<mpl::ir::FunctionDeclNode>();
ctor->name = "__init__";
ctor->isConstructor = true;
ctor->parameters = { {"self", "self"}, {"x", "x"}, {"y", "y"} };
// body: self.x = x; self.y = y
cls->constructor = ctor;
auto originStatic = std::make_shared<mpl::ir::FunctionDeclNode>();
originStatic->name = "origin";
originStatic->isStatic = true;
originStatic->decorators.push_back(std::make_shared<mpl::ir::IdentifierNode>("staticmethod"));
// body: return Point(0, 0)
cls->methods.push_back(originStatic);
auto distanceProp = std::make_shared<mpl::ir::FunctionDeclNode>();
distanceProp->name = "distance";
distanceProp->decorators.push_back(std::make_shared<mpl::ir::IdentifierNode>("property"));
// body: return (self.x ** 2 + self.y ** 2) ** 0.5
cls->methods.push_back(distanceProp);
C#
public class Point : Shape
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y) { X = x; Y = y; }
public override double Area() => X * Y;
public static Point Origin => new Point(0, 0);
}
Lowered IR:
auto cls = std::make_shared<mpl::ir::ClassDeclNode>();
cls->name = "Point";
cls->baseClass = std::make_shared<mpl::ir::IdentifierNode>("Shape");
cls->metadata["visibility"] = "public";
// Properties X and Y (translated to getter + setter methods)
auto xGetter = std::make_shared<mpl::ir::FunctionDeclNode>();
xGetter->name = "get_X";
xGetter->metadata["is_getter"] = "true";
cls->methods.push_back(xGetter);
auto xSetter = std::make_shared<mpl::ir::FunctionDeclNode>();
xSetter->name = "set_X";
xSetter->metadata["is_setter"] = "true";
cls->methods.push_back(xSetter);
// ...similar for Y...
// Constructor
auto ctor = std::make_shared<mpl::ir::FunctionDeclNode>();
ctor->name = ".ctor";
ctor->isConstructor = true;
ctor->parameters = { {"x", "int x"}, {"y", "int y"} };
// body: this.X = x; this.Y = y
cls->constructor = ctor;
// Area method with override
auto areaMethod = std::make_shared<mpl::ir::FunctionDeclNode>();
areaMethod->name = "Area";
areaMethod->metadata["override"] = "true";
areaMethod->metadata["return_type"] = "double";
// body: return X * Y (or expressionBody)
cls->methods.push_back(areaMethod);
// Static property Origin
auto originProp = std::make_shared<mpl::ir::FunctionDeclNode>();
originProp->name = "get_Origin";
originProp->isStatic = true;
originProp->metadata["is_getter"] = "true";
// body: return new Point(0, 0)
cls->methods.push_back(originProp);
Java
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public double distance() {
return Math.sqrt(x * x + y * y);
}
}
Lowered IR:
auto cls = std::make_shared<mpl::ir::ClassDeclNode>();
cls->name = "Point";
cls->metadata["visibility"] = "public";
auto xField = std::make_shared<mpl::ir::VariableDeclNode>();
xField->name = "x";
xField->metadata["visibility"] = "private";
cls->members.push_back(xField);
auto yField = std::make_shared<mpl::ir::VariableDeclNode>();
yField->name = "y";
yField->metadata["visibility"] = "private";
cls->members.push_back(yField);
auto ctor = std::make_shared<mpl::ir::FunctionDeclNode>();
ctor->name = "Point";
ctor->isConstructor = true;
ctor->parameters = { {"x", "int x"}, {"y", "int y"} };
cls->constructor = ctor;
auto distanceMethod = std::make_shared<mpl::ir::FunctionDeclNode>();
distanceMethod->name = "distance";
distanceMethod->metadata["visibility"] = "public";
distanceMethod->returnTypeInfo = std::make_shared<mpl::ir::UIRType>(mpl::ir::UIRType{TypeKind::Float64});
// body: return Math.sqrt(x * x + y * y)
cls->methods.push_back(distanceMethod);
Decorator / attribute handling
ClassDeclNode::decorators is a vector<EMPLNodePtr> — each entry is one of:
IdentifierNode— a bare-name decorator (@Component).FunctionCallNode— a parameterized decorator (@Route("/users")).MemberAccessNode— a namespaced decorator (@core.Inject).
The C++ backend processes decorators in declaration order. The exact emission depends on MPL_DECORATOR_STYLE:
| Environment value | Emission style |
|---|---|
MPL_DECORATOR_STYLE=attribute (default) |
[[decorator_name]] (C++23) or __attribute__((...)) (GCC/Clang) |
MPL_DECORATOR_STYLE=annotation |
// @decorator_name (Java-style) — comment, not active |
MPL_DECORATOR_STYLE=call |
Emit a call to decorator_name(cls) before the class definition |
For parameterized decorators (@Route("/users")), the call-style emission is preferred. The C++ backend rewrites:
@Route("/users", methods = ["GET", "POST"])
class UserController { /* … */ }
to:
inline void __decorator_Route(/* … */);
// …
__decorator_Route(UserController::class_, "/users", {"GET", "POST"});
class UserController { /* … */ };
Static initialization blocks
ClassDeclNode::staticBlocks is a vector<EMPLNodePtr> — each entry is a BlockNode. Used for:
class Registry {
static items = [];
static {
items.push("a");
items.push("b");
}
}
The C++ backend emits:
class Registry {
public:
static std::vector<std::string> items;
Registry() = default;
};
namespace _mpl_Registry_static_init {
inline struct Init {
Init() {
Registry::items.push_back("a");
Registry::items.push_back("b");
}
} _init;
}
std::vector<std::string> Registry::items;
The _init struct runs at static-initialization time, before main().
Inheritance handling
For multiple inheritance (C++ only):
class Derived : public Base1, public Base2 { /* … */ };
The IR does not model multiple inheritance — ClassDeclNode::baseClass is a single EMPLNodePtr. For multiple inheritance, the C++ backend emits a wrapper class that inherits both, OR the frontend flattens multiple bases into a single mixed-in class (depending on MPL_MULTIPLE_INHERITANCE_MODE).
See also (post-summary)
nodes/function-decl.md—FunctionDeclNodenodes/variable-decl.md—VariableDeclNodetypes.md—UIRTypevalidator.md— rule V9 (class invariants)