mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 22:28:45 +01:00
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.
This commit is contained in:
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
34
test/language/inheritance/inherit_from_enclosing.wren
Normal file
34
test/language/inheritance/inherit_from_enclosing.wren
Normal file
@ -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.
|
||||
15
test/language/inheritance/inherited_shadows_enclosing.wren
Normal file
15
test/language/inheritance/inherited_shadows_enclosing.wren
Normal file
@ -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
|
||||
Reference in New Issue
Block a user