From a5034ca41aaf176056f0e9abee5c0c1614f3fbfc Mon Sep 17 00:00:00 2001 From: "Shea Lewis (Kai)" <355659+kaidesu@users.noreply.github.com> Date: Tue, 13 Jan 2026 01:15:08 -0800 Subject: [PATCH] fix inherited constructor invocation --- evaluator/evaluator_test.go | 73 +++++++++++++++++++++++++++++++++++++ object/class.go | 15 ++++++-- object/instance.go | 12 ++++-- 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index c320388..1b74a4b 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -402,6 +402,79 @@ func TestInheritedProperties(t *testing.T) { } } +func TestInheritedConstructor(t *testing.T) { + tests := []struct { + name string + input string + expected int64 + }{ + { + name: "parent constructor called when child has none", + input: ` + class Animal { + function constructor(legs) { + this.legs = legs + } + } + + class Dog extends Animal { + } + + d = Dog.new(4) + d.legs + `, + expected: 4, + }, + { + name: "grandparent constructor called when child and parent have none", + input: ` + class Animal { + function constructor(legs) { + this.legs = legs + } + } + + class Mammal extends Animal { + } + + class Dog extends Mammal { + } + + d = Dog.new(4) + d.legs + `, + expected: 4, + }, + { + name: "child constructor overrides parent", + input: ` + class Animal { + function constructor(legs) { + this.legs = legs + } + } + + class Dog extends Animal { + function constructor(legs) { + this.legs = legs * 2 + } + } + + d = Dog.new(4) + d.legs + `, + expected: 8, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := evaluate(tt.input) + isNumberObject(t, result, tt.expected) + }) + } +} + func TestThisOutsideClass(t *testing.T) { tests := []struct { name string diff --git a/object/class.go b/object/class.go index 5377f08..ffe0577 100644 --- a/object/class.go +++ b/object/class.go @@ -31,12 +31,19 @@ func (class *Class) Method(method string, args []Object) (Object, bool) { case "new": instance := &Instance{Class: class, Environment: NewEnclosedEnvironment(class.Environment)} - if ok := instance.Environment.Has("constructor"); ok { - result := instance.Call("constructor", args, class.Name.Token) + // Check for constructor in class and superclass chain + currentClass := class + for currentClass != nil { + if constructor, ok := currentClass.Environment.Get("constructor"); ok { + result := instance.callMethod(constructor.(*Function), args, class.Name.Token) - if result != nil && result.Type() == ERROR { - return result, false + if result != nil && result.Type() == ERROR { + return result, false + } + + break } + currentClass = currentClass.Super } return instance, true diff --git a/object/instance.go b/object/instance.go index 8cc12d2..bfd1399 100644 --- a/object/instance.go +++ b/object/instance.go @@ -32,16 +32,20 @@ func (instance *Instance) Method(method string, args []Object) (Object, bool) { func (instance *Instance) Call(name string, arguments []Object, tok token.Token) Object { if function, ok := instance.Environment.Get(name); ok { if method, ok := function.(*Function); ok { - methodEnvironment := createMethodEnvironment(method, arguments) - methodScope := &Scope{Self: instance, Environment: methodEnvironment} - - return evaluator(method.Body, methodScope) + return instance.callMethod(method, arguments, tok) } } return NewError("%d:%d: runtime error: unknown method '%s' on class %s", tok.Line, tok.Column, name, instance.Class.Name.Value) } +func (instance *Instance) callMethod(method *Function, arguments []Object, tok token.Token) Object { + methodEnvironment := createMethodEnvironment(method, arguments) + methodScope := &Scope{Self: instance, Environment: methodEnvironment} + + return evaluator(method.Body, methodScope) +} + func createMethodEnvironment(method *Function, arguments []Object) *Environment { env := NewEnclosedEnvironment(method.Scope.Environment)