-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[dart:js_interop/dart2wasm] Missing type parameter check in exported generic callback #54314
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
Comments
/cc @osa1 |
It looks like interop closures (those converted to The reason why
Now the question is, does void test<T extends A>() {
final f = (T t) {};
(f as Function(A))(A()); // static error
} The only way I could find is by In the JS case, we can't have something like the forwarders we have in Dart, because in a declaration like
The function being called may not be a Dart function with a runtime type that we can use to type check against the argument. So I think the right place to introduce the type check is when we generate the Dart for the closure's // The thing assigned to `jsFunction` should do the type check.
jsFunction = ((T t) {
return t;
}).toJS; |
Ah, good to have context that these checks are done elsewhere in a "forwarder" and not in the methods themselves. That makes sense why non-exported functions are okay. There are complications with adding this check in when we do What is interesting is that this code: import 'dart:js_interop';
@JS()
external JSExportedDartFunction get jsFunction;
@JS()
external set jsFunction(JSFunction f);
void main() {
void test<T extends JSAny?>() {
jsFunction = ((T t) {
return t;
}).toJS;
final f = jsFunction.toDart;
f(null);
}
test<JSAny>();
} does throw. I'd expect the mechanisms to be very similar in the case when we call through |
That code also throws for the same reason. Return type of Something else I noticed is we also don't check the "shape" of the function being called. For example, I can call a closure that takes no args, with args: import 'dart:js_interop';
@JS('jsFunction')
external void callJsFunction(JSAny? v1, JSAny? v2);
@JS()
external set jsFunction(JSFunction f);
void main() {
void test<T extends JSAny?>() {
jsFunction = (() {
return 1;
}).toJS;
callJsFunction(1.toJS, 2.toJS); // Dart closure takes no arguments, but this succeeds
}
test<JSAny>();
} This program doesn't throw any exceptions. Here's how it works today (mostly for my own benefit, first time working on this parts of the compiler): The relevant JS imports: _232: (x0,x1) => globalThis.jsFunction(x0,x1), // void callJsFunction(v1, v2)
_233: x0 => globalThis.jsFunction = x0, // set jsFunction The JS export for calling the Dart closure from JS: (func $_234 (;84;) (export "_234") (param $var0 anyref) (result externref)
(local $var1 (ref $#Closure-0-0))
local.get $var0
ref.cast $#Closure-0-0
local.tee $var1
struct.get $#Closure-0-0 $field2
local.get $var1
struct.get $#Closure-0-0 $field3
struct.get $#Vtable-0-0 $field1
call_ref $type301
call $jsifyRaw
return
ref.null noextern) Note that this only takes a closure argument, because the Dart closure doesn't take any arguments. Wasm for (func $callJsFunction (;693;) (param $var0 (ref null $JSValue)) (param $var1 (ref null $JSValue)) (result (ref null $#Top))
local.get $var0
call $JSValue.unbox
local.get $var1
call $JSValue.unbox
call $dart2wasm._232 (import)
call $dartifyRaw
drop
ref.null none)
// The setter
(func $jsFunction (;691;) (param $var0 (ref $JSValue))
local.get $var0
call $JSValue.unbox
call $dart2wasm._233 (import)
call $dartifyRaw
drop) The setter is called here: (func $main closure at file:///usr/local/google/home/omersa/dart/sdk_wasm/test/test.dart:10:30 (;687;) (param $var0 (ref struct)) (param $var1 (ref $_Type)) (result (ref null $#Top))
...
struct.new $#Closure-0-0 ;; allocate Dart closure
call $jsObjectFromDartObject ;; calls extern.convert_any, converts `ref any` to `ref extern`
call $dart2wasm._234 (import) ;; imports `f => finalizeWrapper(f,() => dartInstance.exports._234(f))`
call $JSValue.box
ref.as_non_null
call $jsFunction ;; sets jsFunction JS global = `finalizeWrapper(f, () => ...)`
global.get $global328
call $NumToJSExtension|get#toJS ;; first arg
global.get $global40
call $NumToJSExtension|get#toJS ;; second arg
call $callJsFunction ;; call JS global
drop
ref.null none)
function finalizeWrapper(dartFunction, wrapped) {
wrapped.dartFunction = dartFunction;
wrapped[jsWrappedDartFunctionSymbol] = true;
return wrapped;
} The reason why this program works without any errors is because the closure being from JS is this argument to It also works when I don't pass required arguments, because in JS land unpassed args become "undefined" 😃 Getting back to the question, in the call sequence:
If I understand correctly, the export Type checks could be done by the closure itself, but that would make calling it from Dart inefficient as we'd be redundantly checking types. (I'm ignoring optional positional and named arguments, I'm assuming we either don't allow those in functions converted to JS, or the order of things is somehow handled correctly in (2)) |
Sorry for the late response! This fell into the holiday void backlog. :)
Ah, yeah, this relates to your comment above regarding the forwarder. I wonder if the solution is we should cast the callback as
I think passing in too many args is actually okay and in some cases, desired by users e.g. #48186. :) We should be checking if we have too few args, though, but I think that code was only specialized for callbacks that take optional parameters. In the case where we have optional parameters, the JS function that we pass to (int a, [int b = 50]) {
return b;
}.toJS; At any rate, we can adopt the convention of passing
Yup! I've probably abused that term in various contexts, so I should be more clear here.
We don't allow named arguments in callbacks (I don't think you can call them in JS even if you wanted to?), just in object literal constructors. Also, out of curiosity, what are you using to view Wasm files? VS code + the Wasm extension keeps mangling the module that dart2wasm outputs. |
test<JSAny>
does not produce any errors, whiletest2<JSNumber>
does the correct check on the parameter.I had to disable some checks and remove some casts from the transformer to get a third test to work:
but this also does not do a type-check, indicating that something about exporting the function is affecting the type-checking.
The callback should be called in the trampoline function, which is in Dart, so this shouldn't be an issue of the error escaping through JS.
The text was updated successfully, but these errors were encountered: