-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
dart2js incorrectly assumes getter on a JS interop abstract class is null #44692
Comments
@sigmundch @natebosch - I'm not sure who's best to field JS interop related questions. |
cc @srujzs |
It's not totally surprising that we still have patterns that work in DDC but not dart2js. I'm not sure what could be happening in this case though. I suspect the most straightforward solution would be to implement a working |
I tried to dig more into this today by creating a simple JS async iterator (without the file system stuff), and was unable to reproduce at all. The dart2js output is much larger, but it seems to work. So I tried again in my real project, and also couldn't reproduce this error - the app wasn't working properly, but it turned out to be that in dart2js output I couldn't compare the print(item.kind == 'file'); // true in ddc, but false in dart2js
print(getProperty(item, 'kind') == 'file'); // true in both This seems to be because I'd missed the I suspect that the original bug I saw was the latter (the Sorry for the confusion. I'll re-open this if I see it again and confirm it's not just an old SDK. Thanks! |
Thanks for the update @DanTup - by any chance can you point me at the definition of @rileyporter and @srujzs have added several static checks to ensure that our JS-interop definitions are treated consistently between DDC and dart2js. It's possible that the issue you ran into is something that we'll be able to detect in the near future, but it would be good to know if there are more edge cases we need to consider. |
@sigmundch it's the @JS()
abstract class FileSystemHandle {
String get kind;
String get name;
} And adding the Out of interest - how are the JS bindings normally created here? Is it done manually, or is it scripted from some API docs? I've found a few missing/incomplete bindings (for APIs like this file picker and media session) and have been just creating my own - though I don't know how well I'm doing it and it seems like the sort of thing that might have been automated? |
The generation of Most users maintain the js-bindings by hand, but there are tools like https://github.com/dart-lang/js_facade_gen to help automate the process if you happen to have .d.ts definitions available. As for your example above, If |
@sigmundch thanks! I suspect there are TypeScript bindings available for these APIs, so I might try js_facade_gen when I get back to this to see what it generates compared to mine.
As far as I can tell, |
Regarding your comment earlier out of curiosity: As a side note, most likely not an issue if we don't add a |
It was before I added
That's fine to me - if I start seeing errors like that, it'll be a signal I can switch over to SDK-supplied classes (which would be better) :-) |
Hmm, it is indeed unexpected. I wrote a small test verifying behavior for non-external fields, and I don't see inconsistent behavior before and after they're modified. Can you write a small repro when you get the chance? Thanks! Interestingly enough, however, non-external fields have the same semantics as external fields. We should clarify the semantics here since that may be unintuitive. The following passes on both compilers: Testing non-external fields
|
@srujzs my repro is below. It calls Adding If this seems like a bug, feel free to move to a new issue (or let me know if you want me to raise one). Thanks! web/index.html<html>
<head>
<script defer src="main.dart.js"></script>
</head>
<body>
<button id="pickMusic">Pick Music Folder</button>
</body>
</html> web/main.dart@JS()
library js_lib;
import 'dart:html';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
main() {
document.querySelector('#pickMusic').onClick.listen((_) => click());
}
click() async {
final dir = await showDirectoryPicker();
for (var file in await dir.values().toList()) {
if (file.kind == 'file') {
print('found a file!');
} else if (file.kind == 'directory') {
print('found a directory!');
} else {
print('found a ${file.kind} which is neither a file or a directory');
}
}
}
Future<FileSystemDirectoryHandle> showDirectoryPicker() =>
promiseToFuture(callMethod(window, 'showDirectoryPicker', []));
Stream<T> _asyncIterator<T>(jsIterator) async* {
while (true) {
final next = await promiseToFuture(callMethod(jsIterator, 'next', []));
if (getProperty(next, 'done')) {
break;
}
yield getProperty(next, 'value');
}
}
@JS()
abstract class FileSystemDirectoryHandle extends FileSystemHandle {}
@JS()
abstract class FileSystemFileHandle extends FileSystemHandle {}
@JS()
abstract class FileSystemHandle {
String get kind;
String get name;
}
extension FileSystemDirectoryHandleExtensions on FileSystemDirectoryHandle {
Stream<FileSystemHandle> values() =>
_asyncIterator<FileSystemHandle>(callMethod(this, 'values', []));
}
extension FileSystemHandleExtensions on FileSystemHandle {
Future<File> getFile() => promiseToFuture(callMethod(this, 'getFile', []));
} |
Interesting - it appears that the fact that the class is labeled Internally it seems we believe the result of the getter is always null, and as a result we unsoundly optimize away the equality checks against Shrinking your example: import 'package:js/js.dart';
@JS()
abstract class A {
String get kind;
}
@JS()
external A get a;
main() => print(a.kind == 'hi'); This generates a main method that looks like: main: function() {
J.get$kind$x(self.a);
H.printString(H.S(C.JSBool_methods.toString$0(false)));
}, We call the getter in case there are side-effects, but we ignore the return value in this case. |
@sigmundch should I file an issue about that / re-open this one? |
Let's re-open and rename this one. |
I suspect this is an error in how I'm trying to do this rather than a bug (in which case maybe there's missing docs or they're not easy enough to find - I can't find anything or any examples that seem to do the same). It seems strange that it works for
build_runner serve
code but fails forbuild_runner serve --release
code though.I'm trying to call the JavaScript API
window.showDirectoryPicker()
which returns aFileSystemDirectoryHandle
that hasvalues()
async iterator to enumerate over the items in the folder.Using this directly in JavaScript would look like this:
My attempt to call this from Dart started like this:
However this fails in both debug/release output with:
and:
I thought I'd got a solution by calling the
next()
method on the iterator manually in an extension method on the class:This worked great while running with
build_runner serve
, however when I came to try andbuild
(or useserve --release
) it fails with:Which I've been unable to get to debug or understand.
The text was updated successfully, but these errors were encountered: