@jamshark70 I’ve recently fixed the fact you couldn’t return from try sclang: fix non-local return by JordanHendersonMusic · Pull Request #7449 · supercollider/supercollider · GitHub so that will no longer be the case soon (hopefully).
Giving this thread some more thought I actually think this is a flaw in the language and don’t know how to proceed.
Here is a walk through, because this wasn’t discussed.
A {
foo { try {^true} }
}
Above the ^ means jump up the call stack until you find A:foo and make true the return value (push it on to the stack).
However, prTry looks like this (I’ve added some debug).
prTry {
var result, thread = thisThread;
var next = thread.exceptionHandler,
wasInProtectedFunc = Exception.inProtectedFunction;
thread.exceptionHandler = {|error|
thread.exceptionHandler = next; // pop
^error
};
Exception.inProtectedFunction = true;
'before this.value'.postln;
result = this.value;
'after this.value'.postln;
result.debug(\result);
Exception.inProtectedFunction = wasInProtectedFunc;
thread.exceptionHandler = next; // pop
^result
}
Now when we get to this.value, the call stack looks like this…
Interpreter.blahblah
A.foo
Function.try
Function.prTry
Then we enter the function provided to try, i.e. this in Function. When we hit the return, we skip up out of Function.prTry, over Function.try, over whatever else is in A.foo and straight back into the interpreter. The fact the code after this isn’t evaluated is still a bit of a mystery to me, but the error mechanism is borked at this point as the following is never evaluated.
Exception.inProtectedFunction = wasInProtectedFunc;
thread.exceptionHandler = next; // pop
Because we can pass functions with ^ inside them, and they return to whatever method they were defined in, it isn’t possible to check this with syntax, nor at compile time.
The only ‘solution’ I can think of is to emit an error at runtime informing the user this has happened.
The implementation would look something like this…
- Add some kind of macro/annotation that adds a flag to the
PyrMethod, perhaps like such
Function {
prTry ##['CannotBeReturnedOver'] { ... }
}
- Add this as a boolean flag to
PyrMethod. - In
returnFromMethod, when walking up the stack, check that no methods that cannot be skipped are in fact skipped and emit a warning if so.
This raises the more general point that in supercollider today, you can do this non-local return absolutely anywhere.
A {
*foo {
var f = { ^'returnFromFoo' };
A.doStuff(f);
}
*doStuff { |f|
'before f'.postln;
f.();
'after f'.postln;
}
}
'after f' is never posted and A.doStuff does not return this as expected by the syntax of the code.
In general, it is absolutely impossible to always do something after calling a function, and once you call a function, you no longer have control over what will be returned from the method.
This means that all methods that do cleanup, can subtly be subverted.
What we need is something like protect, but at the language level implemented inside the interpreter. I am unsure how to do this…