Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass nonmatching keyword args to the method call - doesNotUnderstand - Edit in PyrMessage.cpp (help) #6064

Open
JordanHendersonMusic opened this issue Aug 6, 2023 · 4 comments · May be fixed by #6297

Comments

@JordanHendersonMusic
Copy link
Contributor

Looking for help solving an issue as it involves the lang code and I am struggling to understand it.

Motivation

For some class

Test {
	doesNotUnderstand{|...a|
		a.postln
	}
}
Test().foo(1, 2, bar: 4)

prints

WARNING: keyword arg 'bar' not found in call to ErTest:doesNotUnderstand
[ foo, 1, 2 ]

It would allow the creation of transparent wrapper classes if this would instead return

[foo, 1, 2, bar, 4]

meaning you could call code before and after all methods.

I've written a little Buffer wrapper that tests to see if it has loaded on the server (and optional waits) before every method call, meaning the user never has to call s.sync again, it is completely transparent and works every where I have tested... except with named args method calls. This would be a huge simplification for the user.

Plan for Implementation

I think the problem lies here:

HOT void executeMethodWithKeys(VMGlobals* g, PyrMethod* meth, long allArgsPushed, long numKeyArgsPushed) {

and that the issue is simply a matter of copying all the arguments over.
This section...

for (j = 1; j < methraw->posargs; ++j, ++name) {

...copies only the args that match the current argument names.
Is it possible to copy the rest that do not match over, but placed at the end of the list so it doesn't interfere with existing code?
I cannot seem to quite figure it out, is there any one who still understands this part of supercollider around?

There is one place where this will not work though, which is when the method arg name matches in doesNotUnderstand, e.g.,

Test {
	doesNotUnderstand{|...a|
		a.postln
	}
}
Test().foo(1, 2, a: 4)

prints

4

The other option would be to create a doesNotUnderstandWithKeys in the class library. There is a cpp function doesNotUnderstandWithKeys but no corresponding method in Object - this is probably a bigger change though.

I'm also gonna post this over on scsynth.

Thanks,
Jordan

@telephon
Copy link
Member

telephon commented Aug 28, 2023

Just to be clear, the warning itself has nothing to do with doesNotUnderstand. It is what happens in general when you call a method and the argument name is not found.

But it seems like a very good idea, and very useful to have.

Perhaps it is possible to have a special case in the case no selector is found, and call another method, doesNotUnderstandWithKeys. By default, doesNotUnderstandWithKeys would then call doesNotUnderstand, with the arguments – this is important, because a lot of code depends on it.

Object {

    doesNotUnderstandWithKeys { |selector, argNamePairs|
          ^this.doesNotUnderstand(selector, argNamePairs[1,3..])
    }
}

That method doesNotUnderstandWithKeys then can be re-routed, e.g. in the case of IdentityDictionary to do something reasonable, just like doesNotUnderstand.

@JordanHendersonMusic
Copy link
Contributor Author

JordanHendersonMusic commented Aug 30, 2023

I've thought about this some more and the conversation over on scsynth was useful to, although the recommendation there was that this was a bad idea!

I think the interface would have to be...

Object{
   doesNotUnderstandWithKeys {|selector, argsArray, namedArgsArray|
      ^this.doesNotUnderstand(selector, *argsArray) // as a default
   }
}

This way, on the stack you can push the receiver and selector, followed by two new arrays containing all the args.
I've read some of the documentation on writing primitives and could probably give this ago at some point... but I'm a little busy right now, so might have to wait.

As I understand it...

This line in cpp doesNotUnderstandWithKeys needs to be updated to call the new sc method, along with registering it in the global.

https://github.com/supercollider/supercollider/blob/ef627ce2c564fe323125234e4374c9c4b0fc7f1d/lang/LangSource/PyrMessage.cpp#L724C1-L725C1

Then the new stuff needs to be pushed on the stack. I don't know if this should overwrite the old stuff?

In that function there is a large number of nested ifs ... I don't know what they do, but at the end it calls blockValueWithKeys. I think this is like exectueWithKeys, but on primitives?

The final line of this function would then just call cpp executeMethod.

executeMethodWithKeys(g, meth, numArgsPushed + 1, numKeyArgsPushed);

@telephon
Copy link
Member

telephon commented Sep 5, 2023

This line in cpp doesNotUnderstandWithKeys needs to be updated to call the new sc method, along with registering it in the global.

https://github.com/supercollider/supercollider/blob/ef627ce2c564fe323125234e4374c9c4b0fc7f1d/lang/LangSource/PyrMessage.cpp#L724C1-L725C1

This is the optimisation for the uniqueMethod interface (i.e. when you have used addUniqueMethod(...), this is how it gets called.

You really should just need doesNotUnderstandWithKeys, but I am not sure how to get sclang to actually call it. For introspection, I have tried to add this to Object:

doesNotUnderstandWithKeys { arg selector ... args;
		^args
	}

But it isn't called by, e.g. nil.pinguin(x: 8).

So maybe someone who has a bit more knowledge about this part of the engine could chime in?

@JordanHendersonMusic
Copy link
Contributor Author

Fixed by #6227

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants