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:
Method resolution order
find_method now walks up the inheritance chain if the method isn't defined directly on the class:
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:
Then evaluating a super.speak() expression:
- Look up
superin the environment chain (resolved via the resolver, exactly likethis) to get the superclassLoxClass. - Look up
this(also in the environment chain, one scope closer) to get the actual instance the method is running on. - 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. bindthat method tothis(the real, possibly-more-derived instance) before calling it — this is what lets a superclass method that callsthis.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). superoutside 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" forthis.- Constructors and inheritance: if
Dogdoesn't defineinit, callingDog("Rex")should fall back toAnimal'sinitvia the samefind_methodchain — verify yourLoxClass.call/aritylogic usesfind_method("init"), not a directself.methods.get.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…