Open leonsenft opened 7 months ago
I'm realizing the solution looks like it might be (a) if I define the Signal
itself as an extension type as well?
Which I'm currently blocked on doing because extension types require @JS()
from dart:js_interop
which preclude me from using arbitrary generic types.
If we had a JSUnsafeDartObject
or something similar that is purely a static interface for an arbitrary Dart value, then I can imagine writing this as:
import 'dart:js_interop';
@JS()
extension type Signal(JSFunction _) implements JSFunction {
external JSUnsafeDartObject call();
}
but this still doesn't allow arbitrary generic types. How important is that ability? Can something like this work:
import 'dart:js_interop';
@JS()
extension type Signal<T>(JSFunction _) implements JSFunction {
@JS('call')
external JSUnsafeDartObject _call();
T call() => _call().toDart as T;
}
? Presumably, dart2js can inline functions as needed and the generated code should look the same as if you used the generic in the external
(which would also implicitly have a cast to T
).
(b) or add an API–perhaps to dart:js_interop_unsafe along the lines of callAsFunction(signal) that translates to signal().
There actually is a callAsFunction
, but that lowers to JS' call
. :) But yes, even though we can implement JSFunction
for an interop extension type, you still don't have a mechanism to call it such that the resulting JS syntax looks like ()
and not .call()
. That'll need to be added.
Also, you can probably annotate the T call() => _call.toDart as T;
method with a pragma to elide the cost of the cast if that's a cost concern.
With the introduction of extension types I'm now able to define:
import 'dart:js_interop';
extension type Signal<T>(JSFunction _) implements JSFunction {
// callAsFunction():
T call() => _trustAs(callAsFunction());
}
@pragma('dart2js:as:trust')
T _trustAs<T>(Object? value) => value as T;
This solves my type issues, however, the implementation of callAsFunction()
still emits function.call()
in the generated JS code rather than simply function()
. Since this type is a JSFunction
, it should be possible to simply emit the function call directly and drop the call
, right?
Edit: I realized @srujzs already pointed out this exact issue in https://github.com/dart-lang/sdk/issues/54844#issuecomment-1932802349
Regarding a mechanism to do x.call()
=> x()
, this is likely something we can do in a lowering by adding a helper method.
This would be similar to how we convert callMethod
(which uses .apply
and creates an array) to _callMethod1
(which passes arguments directly to the method). It would also be similar to how DDC today converts x.call()
into x()
in it's backend when it knows that x
is an interop value.
I'm writing Dart bindings for JS types that essentially are
function
objects with some extra properties tacked on dynamically.I need to be able to both call an instance of
Signal
, and call methods onWritableSignal
. My current implementation supports these requirements usingcall()
to make theSignal
instance callable from Dart:This works, but is suboptimal as it generates
signal.call()
in the resulting JavaScript output. Ideally I'd like to bind directly tosignal()
, but I don't believe this capability exists with JS interop today. I propose either(a) allowing us to define a static interop class that implements/extends some JS function type,
(b) or add an API–perhaps to
dart:js_interop_unsafe
along the lines ofcallAsFunction(signal)
that translates tosignal()
.@srujzs