feat(oop): ::super:Method() dispatch for inheritance chains
Harbour's ::super: idiom routes a method call through the parent of
the class that defines the currently-executing method — Self stays
the child instance, only the vtable entry point shifts. Five
previously parsed ::super as a data-field access (PushSelfField("SUPER"))
which returned nil and panicked on the subsequent Send.
Runtime: Thread.SendSuper(fromClassName, methodName, nArgs).
Binding to the *defining* class (not Self's runtime class) is
load-bearing for 3+ level hierarchies: without it,
Grand:New → ::super:New → Child:New → ::super:New
would resolve to Grand.Parent=Child again and infinite-loop.
Gengo: Generator.curMethodClass tracks the class name across each
method body emission. emitSendExpr detects the nested SendExpr
shape `::super:X(...)` and emits SendSuper with curMethodClass as
the first argument.
Tested (/tmp/test_super, /tmp/test_super2):
Parent → Child: ::super:Greet() returns composed result
Base → Child → Grand: ::super:New chain passes args correctly
Also fixes three gengo unit tests whose expected output was stale
from prior perf commits (b829ed4 const prop, 1f63c7f symbol hoist,
7e4079f string-concat reassoc) — assertions now match the current
optimized codegen.
FiveSql2 43/43, Harbour compat 56/56, Go test ALL PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -284,6 +284,53 @@ func (t *Thread) Send(methodName string, nArgs int) {
|
||||
t.push(t.retVal)
|
||||
}
|
||||
|
||||
// SendSuper dispatches a method call on Self, but starting the method
|
||||
// lookup from the parent of the class that defined the currently-
|
||||
// executing method. Implements `::super:Method(args)`.
|
||||
//
|
||||
// fromClassName is the class whose method body contains the ::super
|
||||
// call — gengo emits it at compile time from the `METHOD ... CLASS X`
|
||||
// declaration. Using Self's runtime class here would infinite-loop on
|
||||
// 3-level hierarchies: Grand:New calls ::super:New → runs Child:New →
|
||||
// Child:New calls ::super:New → would look up Grand.Parent = Child
|
||||
// again, not Child.Parent = Base. Binding to the defining class is
|
||||
// the same technique Harbour uses (method slot carries its origin
|
||||
// class in the vtable).
|
||||
//
|
||||
// Stack: [arg1] ... [argN] → [result].
|
||||
func (t *Thread) SendSuper(fromClassName, methodName string, nArgs int) {
|
||||
args := make([]Value, nArgs)
|
||||
for i := nArgs - 1; i >= 0; i-- {
|
||||
args[i] = t.pop()
|
||||
}
|
||||
|
||||
if !t.self.IsObject() {
|
||||
panic(t.runtimeError("::super: outside method context"))
|
||||
}
|
||||
from := FindClass(fromClassName)
|
||||
if from == nil {
|
||||
panic(t.runtimeError(fmt.Sprintf("::super: unknown defining class %s", fromClassName)))
|
||||
}
|
||||
if from.Parent == nil {
|
||||
panic(t.runtimeError(fmt.Sprintf("class %s has no parent for ::super", from.Name)))
|
||||
}
|
||||
|
||||
parent := from.Parent
|
||||
upper := strings.ToUpper(methodName)
|
||||
fn, ok := parent.Methods[upper]
|
||||
if !ok {
|
||||
panic(t.runtimeError(fmt.Sprintf("unknown method %s in parent class %s", methodName, parent.Name)))
|
||||
}
|
||||
|
||||
// Self unchanged — push args and invoke parent's slot.
|
||||
for _, a := range args {
|
||||
t.push(a)
|
||||
}
|
||||
t.pendingParams = nArgs
|
||||
fn(t)
|
||||
t.push(t.retVal)
|
||||
}
|
||||
|
||||
// SendAssign dispatches a setter: obj:prop := value
|
||||
// Generated for ::fieldName := value
|
||||
func (t *Thread) SendAssign(fieldName string) {
|
||||
|
||||
Reference in New Issue
Block a user