diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index fd80b6e5..4a9ffffd 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -1640,6 +1640,20 @@ static void callSignature(Compiler* compiler, Code instruction, { int symbol = signatureSymbol(compiler, signature); emitShortArg(compiler, (Code)(instruction + signature->arity), symbol); + + if (instruction == CODE_SUPER_0) + { + // Super calls need to be statically bound to the class's superclass. This + // ensures we call the right method even when a method containing a super + // call is inherited by another subclass. + // + // We bind it at class definition time by storing a reference to the + // superclass in a constant. So, here, we create a slot in the constant + // table and store NULL in it. When the method is bound, we'll look up the + // superclass then and store it in the constant slot. + int constant = addConstant(compiler, NULL_VAL); + emitShort(compiler, constant); + } } // Compiles a method call with [numArgs] for a method with [name] with [length]. @@ -2592,6 +2606,16 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants, case CODE_CALL_14: case CODE_CALL_15: case CODE_CALL_16: + case CODE_JUMP: + case CODE_LOOP: + case CODE_JUMP_IF: + case CODE_AND: + case CODE_OR: + case CODE_METHOD_INSTANCE: + case CODE_METHOD_STATIC: + case CODE_LOAD_MODULE: + return 2; + case CODE_SUPER_0: case CODE_SUPER_1: case CODE_SUPER_2: @@ -2609,16 +2633,6 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants, case CODE_SUPER_14: case CODE_SUPER_15: case CODE_SUPER_16: - case CODE_JUMP: - case CODE_LOOP: - case CODE_JUMP_IF: - case CODE_AND: - case CODE_OR: - case CODE_METHOD_INSTANCE: - case CODE_METHOD_STATIC: - case CODE_LOAD_MODULE: - return 2; - case CODE_IMPORT_VARIABLE: return 4; @@ -3224,6 +3238,33 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) fn->bytecode[ip++] += classObj->superclass->numFields; break; + case CODE_SUPER_0: + case CODE_SUPER_1: + case CODE_SUPER_2: + case CODE_SUPER_3: + case CODE_SUPER_4: + case CODE_SUPER_5: + case CODE_SUPER_6: + case CODE_SUPER_7: + case CODE_SUPER_8: + case CODE_SUPER_9: + case CODE_SUPER_10: + case CODE_SUPER_11: + case CODE_SUPER_12: + case CODE_SUPER_13: + case CODE_SUPER_14: + case CODE_SUPER_15: + case CODE_SUPER_16: + { + // Skip over the symbol. + ip += 2; + + // Fill in the constant slot with a reference to the superclass. + int constant = (fn->bytecode[ip] << 8) | fn->bytecode[ip + 1]; + fn->constants[constant] = OBJ_VAL(classObj->superclass); + break; + } + case CODE_CLOSURE: { // Bind the nested closure too. diff --git a/src/vm/wren_debug.c b/src/vm/wren_debug.c index cf50f7d6..9a1b28f6 100644 --- a/src/vm/wren_debug.c +++ b/src/vm/wren_debug.c @@ -215,8 +215,9 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) { int numArgs = bytecode[i - 1] - CODE_SUPER_0; int symbol = READ_SHORT(); - printf("SUPER_%-10d %5d '%s'\n", numArgs, symbol, - vm->methodNames.data[symbol].buffer); + int superclass = READ_SHORT(); + printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol, + vm->methodNames.data[symbol].buffer, superclass); break; } diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index d4c81a1b..745a54c6 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -840,12 +840,10 @@ static bool runInterpreter(WrenVM* vm) int numArgs = instruction - CODE_SUPER_0 + 1; int symbol = READ_SHORT(); - // The receiver is the first argument. Value* args = fiber->stackTop - numArgs; - ObjClass* classObj = wrenGetClassInline(vm, args[0]); - - // Ignore methods defined on the receiver's immediate class. - classObj = classObj->superclass; + + // The superclass is stored in a constant. + ObjClass* classObj = AS_CLASS(fn->constants[READ_SHORT()]); // If the class's method table doesn't include the symbol, bail. if (symbol >= classObj->methods.count) @@ -1218,9 +1216,10 @@ static bool runInterpreter(WrenVM* vm) // or a runtime error. UNREACHABLE(); return false; + + #undef READ_BYTE + #undef READ_SHORT } -#undef READ_BYTE -#undef READ_SHORT // Creates an [ObjFn] that invokes a method with [signature] when called. static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature) diff --git a/test/language/super/super_in_closure_in_inherited_method.wren b/test/language/super/super_in_closure_in_inherited_method.wren new file mode 100644 index 00000000..63d1e672 --- /dev/null +++ b/test/language/super/super_in_closure_in_inherited_method.wren @@ -0,0 +1,11 @@ +class A { + callSuperToString { + return new Fn { super.toString }.call() + } + + toString { "A.toString" } +} + +class B is A {} + +IO.print((new B).callSuperToString) // expect: instance of B diff --git a/test/language/super/super_in_inherited_method.wren b/test/language/super/super_in_inherited_method.wren new file mode 100644 index 00000000..2ee25599 --- /dev/null +++ b/test/language/super/super_in_inherited_method.wren @@ -0,0 +1,9 @@ +class A { + callSuperToString { super.toString } + + toString { "A.toString" } +} + +class B is A {} + +IO.print((new B).callSuperToString) // expect: instance of B