-
Notifications
You must be signed in to change notification settings - Fork 653
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
Add callback forwarding #7813
base: master
Are you sure you want to change the base?
Add callback forwarding #7813
Conversation
Thanks a lot for this great PR! For the other reviewers, this adds support for callback forwarding, which is a syntactic sugar allowing I think this is a great feature to have. The only thing I’m unsure about is whether reusing On the tooling site, this change the callback completion from I'd like to see a bit more tests. The runtime should be tested with an extra file in tests/cases/callbacks. It should test that the feature works including with arguments or return value conversion (eg, int->string and such), as well as forwarding to macros such as Math.max and builtin functions. |
); | ||
assert_eq!( | ||
res.iter().find(|ci| ci.label == "cb2").unwrap().insert_text, | ||
Some("cb2(foo, bar-bar) => {$1}".into()) | ||
); | ||
assert_eq!( | ||
res.iter().find(|ci| ci.label == "cb3").unwrap().insert_text, | ||
Some("cb3 => {$1}".into()) | ||
Some("cb3 => $1".into()) | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you add a test in this file that tests completion for callback forwarding:
foo => 🔺
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's actually no completion for this right now except the generic completion for identifiers/qualified names (i.e. users would have to start typing to get completions and they wouldn't complete the semicolon).
i think a user friendly completion would look like this:
component Foo {
callback foo1;
callback foo2(string, int);
}
component Bar {
callback bar1;
callback bar2(x: string, y: int);
callback bar3(string, x: int);
component Foo {
// callback completion without args
// foo1 => 🔺
// callback completion with args
// foo2🔺
// foo3🔺
// callback connection completion with named args
// foo2(🔺 completes to foo2(x, y) => {🔺}
// callback connection completion with partially named args
// foo3(🔺 does not complete
// callback forwarding completion
// foo1 => 🔺
// foo2 => 🔺
// foo3 => 🔺
// complete with methods from parent element scope that are compatible.
// for example:
// foo1 => bar1;
// foo2 => bar2; or foo2 => bar3;
// foo3 => bar3; or foo3 => bar2;
}
}
this way users aren't forced to delete the completed arguments if they actually wanted to forward a callback with named arguments but still get completion for the callback connection when they type the open parenthesis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot.
I'm happy with the implementation.
I'm just waiting with the other from the team if that is really the syntax we want with =>
.
One drawback is that this prevent us, in the future, to allow to have callback connection without {}
For example, this is not allowed currently, and cannot be allowed in the future with this change, as it would then be ambiguous.
clicked => debug("foo clicked");
double-clicked => root.activate(index);
something(foo) => root.something-else(foo + 1);
The original report in #6373 suggests to use <=>
but this is also misleading since it only goes one way. (calling the callback on the right hand side wouldn't cause the callback on the left hand side to be called) Although since callback can only have one handler, this might not be critical.
Other options include:
:
as inclicked: root.clicked;
(but that looks like a binding)=
as inclicked = root.clicked;
->
as inclicked -> root.clicked;
:>
as inclicked :> root.clicked;
- use a (context sensitive) keyword such as
forward clicked to root.clicked;
- ... imagination is the limit.
In my opinion, repurposing =>
looks good but I'm a bit worried about the ambiguity of having the same symbol means two things.
👍 from me, but would like to also hear more opinions before merging this.
Could you elaborate why this is ambiguous? Just from reading the code this looks different to me from the syntax introduced in this patch. This patch doesn't use parentheses, right?
I think reusing |
Because from a parsing point of view both are expressions. |
My two cents are that at this point the syntax should be unified. So a callback forwarding is just a callback connection where the right hand side is a compatible callback/function. The other cases are then handled separately. From a user perspective, I'd agree that it probably makes sense to allow the right hand side of a callback connection to be every expression that evaluates to the callback's return type (or a convertible one) or a compatible callback/function. This could be realized by treating every callback connection that doesn't have a code block for the right hand side as syntactic sugar like this PR does. This leaves us with the following cases:
Note that case 1.i only works for callbacks with named arguments, although I'd rather always treat it as an error because it is ambiguous where the arguments come from and it breaks when renaming the arguments. I'm interested to know what all of you think. Maybe it would've made more sense to already extend the callback connection syntax instead of introducing a new one. |
My 2 cents. 1. What a delightful improvement! 2. I'm not sure I followed everything, but to me the simplest thing to understand is |
In the future, if we were to support functions as values, we might run into ambiguity like this: callback get-the-callback() -> fn();
SomeComponent {
// get_callback is a callback that returns a callback
get_callback => root.get-the-callback
} In this case, it would be unclear whether we should call Having two similar way may also be an issue in error recovery. But if you all think I'm overthinking this, then I can change my mind. |
I know understand the problem with higher order types and share the concern. |
I share your concerns. The more I think about it, the less I feel like this PR is an elegant or future proof solution. I still think that this can be a unified syntax that has an unambiguous logic for when the right hand side is returned/called though (the approach I outlined above).
Higher order functions wouldn't be a problem as long as there is no type erasure for the returned function type. Let's say that The example posited by @ogoffart falls under case
In order for The error reporting seems straightforward as well:
It's up to you whether this should get merged in this state. After all, it is an improvement in terms of usability but I'm also fine with going back to the drawing board and thinking about a better solution. |
This PR implements a new callback forwarding syntax that reduces boilerplate code for callback connections that only call another callback/function as requested in #6373.