Classes and Inheritance

Published: (February 4, 2026 at 06:02 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

We now move on to complete the interpreter by adding support for classes.
Classes can inherit from other classes (re‑using their properties and methods) while also allowing customisation through modifications and additions.

Classes diagram

What I built

Commits bac3f3c, bfa82b9, 03b3af3, 6c70087

What I understood

  1. Class Declarations

    • The parser creates a Stmt.Class node for each class declaration, containing the class name and its list of methods.
    • The resolver declares the class name in the current scope so the class can refer to itself (e.g., for recursion).
    • When the interpreter visits Stmt.Class, it turns the AST node into a LoxClass object that stores a map of methods (each converted into a LoxFunction).
  2. Class Instances / Objects

    • Lox doesn’t use a new keyword.
    • LoxClass implements the LoxCallable interface, so a class is instantiated whenever it is called.
    • Calling a class creates a new LoxInstance and returns it.
    • The instance keeps a reference to its class (LoxClass) so it can later look up methods.
  3. Properties

    • Fields aren’t declared in the class; they are created on assignment through instances.
    • When the interpreter evaluates an object that is a LoxInstance, it first calls get(), which checks the instance’s field map.
    • If the field exists, its value is returned; otherwise the interpreter looks for a method. If neither is found, a runtime error is thrown.
    • The parser cannot distinguish a set from a get expression until it sees an = sign. It therefore parses the left‑hand side as a regular expression (get) and, on encountering =, converts that node into a set node.
    • When set() is invoked, the fields map is updated (overwriting any existing key).
  4. Methods

    • Lox methods don’t have the fun keyword and are accessed through instances.
    • If a method is extracted (e.g., assigned to a variable) and called later, this must still refer to the instance from which the method was extracted.
    • To achieve this, when a method is accessed, LoxClass calls bind(instance) on the underlying LoxFunction.
    • bind() creates a new environment nested inside the method’s original closure, defines this in that environment, and returns a new LoxFunction.
    • If this is used outside a class, the resolver (which tracks whether we’re inside a class) reports an error; otherwise it resolves this as a normal local variable.
    • The interpreter looks up this in the environment like any other variable, and thanks to the binding step the correct instance is found.

Method binding illustration

  1. Constructors

    • User‑defined constructors are called init.
    • When LoxClass.call finds an init method, it invokes it immediately to set up the new instance.
    • The arity of the call is checked against the initializer’s parameter count.
    • init always returns this (even if the user tries to return another value). The resolver enforces this by tracking an isInitialiser flag in LoxFunction.
  2. Inheritance

    • A subclass can inherit methods from a superclass via a reference stored in the subclass’s LoxClass. Method lookup walks up the inheritance chain.
    • The parser now creates a Stmt.Class node that also contains a superclass field (an Expr.Variable).
    • The resolver checks for self‑inheritance and reports an error if detected.
    • If a superclass exists, the resolver creates a new scope and defines super in it, making super available to all methods of the subclass.
    • While creating a subclass, the interpreter creates a new environment, defines super to point to the superclass, and then creates the methods.
    • Method lookup proceeds by first checking the instance’s fields, then the class’s methods, and finally the superclass’s methods (if any). If the chain ends with null, the lookup fails.

Inheritance diagram

  1. super
    • The super keyword lets subclasses override a method while still being able to call the original implementation from the parent class.
    • Even if a chain of subclasses overrides the same method, super always resolves to the correct immediate parent implementation.

All the above reflects the current state of the Jlox interpreter after adding full class support, including inheritance, constructors, and the super keyword.

Class definition

  • It is followed by a dot and an identifier (super.method) and is parsed as an Expr.Super node.
  • It looks up a method on the superclass but binds it to the current instance (this), with both belonging to different environments.
  • The resolver treats super as a local variable and calculates its distance (the number of hops) from the environment in which super is defined.

Super lookup illustration

What’s next:
Some extensions to Lox—support for lists and for‑in loops.

Musings

Blogging this project has been really good for me. Even though I’m working on other projects at the moment, trying to explain what I did (months ago!) forces me to recall the details and the reasoning behind them. In a way, the insights I gained earlier continue to influence how I approach my current work.

I absolutely love documenting what I do, even if it’s the most mundane activity. In Indian philosophy, work is among the highest acts of devotion. Whatever we do, if we do it with intent, it leads us to competence and mastery, because it’s the process of learning and doing that helps us become our best versions. Tagore put it well:

“Where tireless striving stretches its arms towards perfection…”

Where else will God lie but in that perfection? Hence, I remind myself of all that I’ve learned.

Back to Blog

Related posts

Read more »

Object-Oriented Programming (OOPs)

Class Example: Car is a class. Different cars may have different names and brands, but all share common properties like four wheels, speed limit, and mileage....

Java Features::

Java programming language was initially developed to work on embedded systems, set‑top boxes, and television. By requirement, it was designed to run on varied p...

Simple Rust Program to Add Two Vectors

Linear algebra is the backbone of modern computing. GPUs utilize vector addition to calculate pixel positions and lighting for real‑time 3D rendering. Similarly...

Features of Java

!Cover image for Features of Javahttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.am...