From 271fcec81bb0972ac3a076bd513e74416aaa14d6 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Tue, 17 Dec 2013 09:39:05 -0800 Subject: [PATCH] Start getting superclass constructors working. This also means the metaclass inheritance hierarchy parallels the regular inheritance chain so that the subclass can find the superclass constructor. --- src/wren_compiler.c | 39 +++++++++++++++++--- src/wren_value.c | 15 +++++++- test/constructor/superclass.wren | 25 +++++++++++++ test/inheritance/inherit_methods.wren | 7 ++-- test/inheritance/inherit_static_methods.wren | 23 ++++++++++++ 5 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 test/constructor/superclass.wren create mode 100644 test/inheritance/inherit_static_methods.wren diff --git a/src/wren_compiler.c b/src/wren_compiler.c index 7e7c956f..0df2f695 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -1342,7 +1342,7 @@ static void super_(Compiler* compiler, bool allowAssignment) emit(compiler, CODE_LOAD_LOCAL); emit(compiler, 0); - // TODO: Super operator and constructor calls. + // TODO: Super operator calls. consume(compiler, TOKEN_DOT, "Expect '.' after 'super'."); // Compile the superclass call. @@ -1596,11 +1596,40 @@ void method(Compiler* compiler, Code instruction, bool isConstructor, int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length); - consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); + if (isConstructor) + { + // See if there is a superclass constructor call, which comes before the + // opening '{'. + if (match(compiler, TOKEN_SUPER)) + { + // Push a copy of the class onto the stack so it can be the receiver for + // the superclass constructor call. + emit(&methodCompiler, CODE_LOAD_LOCAL); + emit(&methodCompiler, 0); - // If this is a constructor, the first thing is does is create the new - // instance. - if (isConstructor) emit(&methodCompiler, CODE_NEW); + consume(compiler, TOKEN_DOT, "Expect '.' after 'super'."); + + // Compile the superclass call. + // TODO(bob): What if there turns out to not be a superclass constructor + // that matches this? + namedCall(&methodCompiler, false, CODE_SUPER_0); + + // The superclass call will return the new instance, so store it back + // into slot 0 to replace the receiver with the new object. + emit(&methodCompiler, CODE_STORE_LOCAL); + emit(&methodCompiler, 0); + + // Then remove it from the stack. + emit(&methodCompiler, CODE_POP); + } + else + { + // Otherwise, just create the new instance. + emit(&methodCompiler, CODE_NEW); + } + } + + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); finishBlock(&methodCompiler); // TODO: Single-expression methods that implicitly return the result. diff --git a/src/wren_value.c b/src/wren_value.c index da2d1555..bd81fd77 100644 --- a/src/wren_value.c +++ b/src/wren_value.c @@ -54,7 +54,20 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields) // Make the metaclass. // TODO: What is the metaclass's metaclass? // TODO: Handle static fields. - ObjClass* metaclass = newClass(vm, NULL, vm->classClass, 0); + // The metaclass inheritance chain mirrors the class's inheritance chain + // except that when the latter bottoms out at "Object", the metaclass one + // bottoms out at "Class". + ObjClass* metaclassSuperclass; + if (superclass == vm->objectClass) + { + metaclassSuperclass = vm->classClass; + } + else + { + metaclassSuperclass = superclass->metaclass; + } + + ObjClass* metaclass = newClass(vm, NULL, metaclassSuperclass, 0); // Make sure it isn't collected when we allocate the metaclass. PinnedObj pinned; diff --git a/test/constructor/superclass.wren b/test/constructor/superclass.wren new file mode 100644 index 00000000..8e7be084 --- /dev/null +++ b/test/constructor/superclass.wren @@ -0,0 +1,25 @@ +class A { + this new(arg) { + io.write("A.new " + arg) + } +} + +class B is A { + this otherName(arg1, arg2) super.new(arg2) { + io.write("B.otherName " + arg1) + } +} + +class C is B { + this create super.otherName("one", "two") { + io.write("C.create") + } +} + +var c = C.create +// expect: A.new two +// expect: B.otherName one +// expect: C.create +io.write(c is A) // expect: true +io.write(c is B) // expect: true +io.write(c is C) // expect: true diff --git a/test/inheritance/inherit_methods.wren b/test/inheritance/inherit_methods.wren index 700d7b3f..d6cc4325 100644 --- a/test/inheritance/inherit_methods.wren +++ b/test/inheritance/inherit_methods.wren @@ -2,12 +2,14 @@ class Foo { methodOnFoo { io.write("foo") } method(a) { io.write("foo") } method(a, b, c) { io.write("foo") } + override { io.write("foo") } } class Bar is Foo { methodOnBar { io.write("bar") } method(a, b) { io.write("bar") } method(a, b, c, d) { io.write("bar") } + override { io.write("bar") } } var bar = Bar.new @@ -19,9 +21,6 @@ bar.method(1) // expect: foo bar.method(1, 2) // expect: bar bar.method(1, 2, 3) // expect: foo bar.method(1, 2, 3, 4) // expect: bar +bar.override // expect: bar -// TODO: Overriding. -// TODO: Private fields. -// TODO: Super (or inner) calls. -// TODO: Grammar for what expressions can follow "is". // TODO: Prevent extending built-in types. diff --git a/test/inheritance/inherit_static_methods.wren b/test/inheritance/inherit_static_methods.wren new file mode 100644 index 00000000..e2a07bce --- /dev/null +++ b/test/inheritance/inherit_static_methods.wren @@ -0,0 +1,23 @@ +class Foo { + static methodOnFoo { io.write("foo") } + static method(a) { io.write("foo") } + static method(a, b, c) { io.write("foo") } + static override { io.write("foo") } +} + +class Bar is Foo { + static methodOnBar { io.write("bar") } + static method(a, b) { io.write("bar") } + static method(a, b, c, d) { io.write("bar") } + static override { io.write("bar") } +} + +Bar.methodOnFoo // expect: foo +Bar.methodOnBar // expect: bar + +// Methods with different arity do not shadow each other. +Bar.method(1) // expect: foo +Bar.method(1, 2) // expect: bar +Bar.method(1, 2, 3) // expect: foo +Bar.method(1, 2, 3, 4) // expect: bar +Bar.override // expect: bar