Skip to content

Step 1 of 3 · Reading · ~3 min

Inheritance and super

Closures & Classes

Inheritance & Method Resolution

Single inheritance adds one link to your class model — a class can name a superclass — and one new keyword, super, that needs its own small closure trick very similar to how you bound this.

Parsing and storing the superclass

class Animal { speak() { print "..."; } }
class Dog < Animal { speak() { print "Woof!"; } }

Extend the class declaration grammar to accept an optional < Identifier after the class name, resolved to a variable expression (so it can itself be a class stored in any variable, not just referenced by literal name). At class-creation time, evaluate that expression, check it actually evaluates to a LoxClass (raise a runtime error otherwise — you can't inherit from a number), and store it:

python

Method resolution order

find_method now walks up the inheritance chain if the method isn't defined directly on the class:

python

This single recursive fallback is the entire method resolution order for single inheritance: check the instance's own class first, then its superclass, then its superclass, and so on up to the root. Because Dog defines its own speak, Dog().speak() finds it immediately and never looks at Animal. If Dog didn't override speak, the lookup would fall through to Animal.speak.

super.method() needs its own binding

Inside an overriding method, you sometimes want to explicitly call the parent's version rather than the (possibly further-overridden) version that normal dispatch would find. super.speak() can't just be "start find_method search from the current class" using dynamic self.klass, because that would break with multiple levels of overriding — you need to start the search from the superclass of the class the method was defined in, which is a static fact about where the method text lives, not a runtime fact about the instance.

The standard technique: when binding a method for execution, wrap its closure in an extra environment (same pattern as bind for this) that defines super pointing at the class's superclass:

python

Then evaluating a super.speak() expression:

  1. Look up super in the environment chain (resolved via the resolver, exactly like this) to get the superclass LoxClass.
  2. Look up this (also in the environment chain, one scope closer) to get the actual instance the method is running on.
  3. Call superclass.find_method("speak") to get the unbound method — searching starts one level above the current class, statically, regardless of what subclass the instance actually is.
  4. bind that method to this (the real, possibly-more-derived instance) before calling it — this is what lets a superclass method that calls this.otherMethod() still dispatch to an overridden version if one exists further down.

Edge cases to watch

  • Inheriting from itself: class Oops < Oops { } must be caught (the superclass expression evaluates before the class exists in its own scope, or you check by name).
  • super outside a subclass or a class with no superclass should be a resolver/runtime error — extend the resolver to track "am I currently inside a class that has a superclass" the same way it tracks "am I inside a method" for this.
  • Constructors and inheritance: if Dog doesn't define init, calling Dog("Rex") should fall back to Animal's init via the same find_method chain — verify your LoxClass.call/arity logic uses find_method("init"), not a direct self.methods.get.
Up nextChunks of BytecodeBytecode Virtual Machine

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…