SCLang: superPerform fails when called from outside a method of the class
Environment
- SuperCollider version: 3.14
Steps to reproduce
AA {
foo { "super foo".postln }
bar { "super bar".postln }
}
AB : AA {
foo { ^this.superPerform(\foo) }
bar { "child bar".postln }
}
AB().foo
// super foo
AB().superPerform(\foo)
/* ERROR: Message 'foo' not understood.
Perhaps you misspelled 'foo', or meant to call 'foo' on another receiver?
RECEIVER:
Instance of AB { (0x56307d185df8, gc=88, fmt=00, flg=00, set=00)
instance variables [0]
}
*/
Cause
The line in the primitive that looks up the method does so by looking at the currently executing method, not by looking up the receiver's class.
PyrClass* classobj = slotRawSymbol(&slotRawClass(&g->method->ownerclass)->superclass)->u.classobj;
This is sometimes necessary because the super class isn't always set in the receiver's class object as this code is called during start up. The above code is a hack to get around this.
A Potential Solution
This works, but is also a hack.
Create a global variable for marking when the classes have finished being initialised, for some reason this must be set after Main:startup is called.
void compileSucceeded() {
compiledOK = !(parseFailed || compileErrors);
if (compiledOK) {
compiledOK = true;
compiledOK = initRuntime(gMainVMGlobals, 128 * 1024, pyr_pool_runtime);
if (compiledOK) {
VMGlobals* g = gMainVMGlobals;
g->canCallOS = true;
++g->sp;
SetObject(g->sp, g->process);
runInterpreter(g, s_startup, 1);
g->canCallOS = false;
gHasFinishedCompilingClassLibrary = true;
schedRun();
}
flushPostBuf();
}
}
The you can look it up normally through the receiver if not compiling classes.
PyrClass* classobj;
if (gHasFinishedCompilingClassLibrary) {
classobj = slotRawSymbol(&classOfSlot(recvrSlot)->superclass)->u.classobj;
} else {
classobj = slotRawSymbol(&slotRawClass(&g->method->ownerclass)->superclass)->u.classobj;
if (!isKindOfSlot(recvrSlot, classobj)) {
error("superPerform must be called with 'this' as the receiver.\n");
return errFailed;
}
}
This is still a hack and will fail if you attempt to call foo.superPerform in a method that is in turn called during Main:startup, but it works outside of it.
Another solution would be to check if the class object has been finalised. This involves setting the slots to nil (at the moment they are all uninitialised memory), which I haven't been able to find yet.