supercollider icon indicating copy to clipboard operation
supercollider copied to clipboard

SCLang: superPerform fails when called from outside a method of the class

Open JordanHendersonMusic opened this issue 1 year ago • 0 comments

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.

JordanHendersonMusic avatar Jun 28 '24 08:06 JordanHendersonMusic