Elvelt

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: name must be non-empty.
  • V9: if baseClass is non-null, its kind must be Identifier or MemberAccess.
  • V9: any abstract method (body == nullptr) must have isStatic = 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

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)