From c472a61bfff702f2628a4e1545bd90537e60c7f9 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Mon, 21 Dec 2015 17:05:46 -0800 Subject: [PATCH] Inherit methods from enclosing classes. Like NewtonScript, the inner classes superclasses take precedence, but if not found there then the enclosing classes are searched. This code is still a bit hacky in some corners, but it's a step in the right direction. --- src/vm/wren_value.c | 9 +++- src/vm/wren_value.h | 5 +- src/vm/wren_vm.c | 50 ++++++++++++++++--- .../inheritance/inherit_from_enclosing.wren | 34 +++++++++++++ .../inherited_shadows_enclosing.wren | 15 ++++++ 5 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 test/language/inheritance/inherit_from_enclosing.wren create mode 100644 test/language/inheritance/inherited_shadows_enclosing.wren diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index c0970762..1dfbd4f8 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -48,6 +48,7 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) ObjClass* classObj = ALLOCATE(vm, ObjClass); initObj(vm, &classObj->obj, OBJ_CLASS, NULL); classObj->superclass = NULL; + classObj->enclosingClass = NULL; classObj->numFields = numFields; classObj->name = name; @@ -82,7 +83,8 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) } } -ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, +ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, + ObjClass* enclosingClass, int numFields, ObjString* name) { // Create the metaclass. @@ -92,6 +94,9 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); metaclass->obj.classObj = vm->classClass; + // TODO: Is this what we want? Test. + metaclass->enclosingClass = enclosingClass; + wrenPopRoot(vm); // Make sure the metaclass isn't collected when we allocate the class. @@ -102,6 +107,8 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, wrenBindSuperclass(vm, metaclass, vm->classClass); ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); + + classObj->enclosingClass = enclosingClass; // Make sure the class isn't collected while the inherited methods are being // bound. diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index 702538ff..d901747c 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -375,6 +375,8 @@ struct sObjClass { Obj obj; ObjClass* superclass; + + ObjClass* enclosingClass; // The number of fields needed for an instance of this class, including all // of its superclass fields. @@ -615,7 +617,8 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass); // Creates a new class object as well as its associated metaclass. -ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, +ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, + ObjClass* enclosingClass, int numFields, ObjString* name); void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index e6157160..67fcc82f 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -391,10 +391,36 @@ static void runtimeError(WrenVM* vm) // Aborts the current fiber with an appropriate method not found error for a // method with [symbol] on [classObj]. -static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) +static Method* methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) { + ObjClass* originalClass = classObj; + + classObj = originalClass->enclosingClass; + while (classObj != NULL) + { + if (symbol < classObj->methods.count) + { + Method* method = &classObj->methods.data[symbol]; + if (method->type != METHOD_NONE) + { + // TODO: Doing this lazily is a drag. The problem is we don't know all + // of the enclosing class's methods at the point in time when the inner + // class is created. If we change the grammar to only allow inner + // classes to appear after all method definitions, we could do this + // eagerly. Worth doing? + // Copy it down now so we don't have to walk the enclosing classes + // every time. + originalClass->methods.data[symbol] = *method; + return method; + } + } + + classObj = classObj->enclosingClass; + } + vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.", - OBJ_VAL(classObj->name), vm->methodNames.data[symbol].buffer); + OBJ_VAL(originalClass->name), vm->methodNames.data[symbol].buffer); + return NULL; } // Checks that [value], which must be a function or closure, does not require @@ -681,15 +707,22 @@ static void createClass(WrenVM* vm, int numFields, ObjModule* module) Value name = vm->fiber->stackTop[-2]; Value superclass = vm->fiber->stackTop[-1]; + // TODO: This does the wrong thing when there is no enclosing method (i.e. + // top level code). If we make the top level a class definition then this + // should work provided we make sure that class is in the first slot of the + // first call frame. + ObjClass* enclosingClass = wrenGetClass(vm, + vm->fiber->frames[vm->fiber->numFrames - 1].stackStart[0]); + // We have two values on the stack and we are going to leave one, so discard // the other slot. vm->fiber->stackTop--; - + vm->fiber->error = validateSuperclass(vm, name, superclass, numFields); if (!IS_NULL(vm->fiber->error)) return; - ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, - AS_STRING(name)); + ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), enclosingClass, + numFields, AS_STRING(name)); vm->fiber->stackTop[-1] = OBJ_VAL(classObj); if (numFields == -1) bindForeignClass(vm, classObj, module); @@ -960,12 +993,13 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) goto completeCall; completeCall: - // If the class's method table doesn't include the symbol, bail. + // If the class's method table doesn't include the symbol, look in the + // enclosing classes, or fail if not found there. if (symbol >= classObj->methods.count || (method = &classObj->methods.data[symbol])->type == METHOD_NONE) { - methodNotFound(vm, classObj, symbol); - RUNTIME_ERROR(); + method = methodNotFound(vm, classObj, symbol); + if (method == NULL) RUNTIME_ERROR(); } switch (method->type) diff --git a/test/language/inheritance/inherit_from_enclosing.wren b/test/language/inheritance/inherit_from_enclosing.wren new file mode 100644 index 00000000..a533b9d4 --- /dev/null +++ b/test/language/inheritance/inherit_from_enclosing.wren @@ -0,0 +1,34 @@ +class Outer { + construct new() {} + + def outerBefore() { System.print("Outer before") } + def shadowOuter() { System.print("bad") } + + class Inner { + construct new() {} + + def innerBefore() { System.print("Inner before") } + def shadowInner() { System.print("bad") } + + class MoreInner { + construct new() {} + + def shadowOuter() { System.print("ok") } + def shadowInner() { System.print("ok") } + } + + def innerAfter() { System.print("Inner after") } + } + + def outerAfter() { System.print("Outer after") } +} + +var moreInner = Outer.new().Inner.new().MoreInner.new() +moreInner.outerBefore() // expect: Outer before +moreInner.outerAfter() // expect: Outer after +moreInner.innerBefore() // expect: Inner before +moreInner.innerAfter() // expect: Inner after +moreInner.shadowOuter() // expect: ok +moreInner.shadowInner() // expect: ok + +// TODO: Test how super interacts with enclosing classes. diff --git a/test/language/inheritance/inherited_shadows_enclosing.wren b/test/language/inheritance/inherited_shadows_enclosing.wren new file mode 100644 index 00000000..7bc7273f --- /dev/null +++ b/test/language/inheritance/inherited_shadows_enclosing.wren @@ -0,0 +1,15 @@ +class Base { + def method() { System.print("Base") } +} + +class Outer { + construct new() {} + + def method() { System.print("Outer") } + + class Inner is Base { + construct new() {} + } +} + +Outer.new().Inner.new().method() // expect: Base