There are many invalid things that can be done with JS interop, but we don't have static errors for this.
Here are some examples of things that should be reported (or else, need to be specified and fixed in the DDC/dart2js):
@JS on a class/library member without a @JS on the library@JS on a class member instead of external@JS class (Dart code is silently ignored)@JS classes from a Dart class (also unclear if implements is supported)@JS classErrors should be reported from Analyzer so people can see the problem sooner. (Once DDC migrates to Kernel, need a way of calling into Analyzer to produce these kinds of errors. Or we just have shared code between dart2js/DDC for validating JS interop usage.)
Also it would be lovely to have a specification. These would be useful:
@JS allowed to appear?external allowed to appear?Once DDC migrates to Kernel, need a way of calling into Analyzer to produce these kinds of errors. Or we just have shared code between dart2js/DDC for validating JS interop usage.
My preference is for the latter: I can imagine we'd have a js-interop checker implemented as a kernel-visitor that we can both share.
It would be ideal for the analyzer to give errors/warnings before a compilation failure, if possible.
(It's not clear to me we have a good strategy yet for web/JS-specific analyzer checks, so maybe we need a thread/doc/discussion about that separate from js-interop checking in the compilation pipeline)
Yeah for JS interop, we can have shared checks between DDC/dart2js/Analyzer. That way they show up in IDE as well as compilers.
At the moment I don't really care where the checks live :) ... I'm mostly concerned that JS interop is completely unchecked and there's all kinds of undefined behavior people can, and do stumble into.
(And personally, when I have to touch DDC's implementation code for JS interop, I have no idea what's supposed to be legal or not, so it's really difficult to refactor any of the code that touches that.)
@vsmenon and I just ran into a client internally with another edge, non-`abstract classes, i.e.:
@JS()
library js_interop;
import 'package:js/js.dart';
@JS()
@anonymous
class Animal {
external String get name;
external set name(String name);
}
This should _probably_ be a compile-time error, as it implicitly creates a default constructor.
The correct implementation would be:
@JS()
@anonymous
abstract class Animal {
external String get name;
external set name(String name);
}
Or, with an external factory constructor.
abstract Nitpick: The second class also gets a default constructor, you just can't use it to instantiate the abstract class. You can extend the class, though, which should probably also be disallowed.
additional checks that we have discussed recently:
@JS declarations are unnecessary, so we can also warn about any use of them, but we can be a bit more lenient with benign annotations and only warn when there the rename is clearly the intent.@JS and native types: to help users detect issues like #42200, we can pre-build a list of blacklisted names that cannot be bound via @JS and issue a warning on those.I'm curious: what's blocking us from allowing instance method renames? I was talking a bit about this last week and figured it would be a good way to wrap overloading.
what's blocking us from allowing instance method renames?
The current interop design in dart2js does not allow it - though we can do it easily in ddc.
In dart2js all @JS() interop classes share effectively a single implementation. When the dart code has foo.bar() that ends up going through to a Dart method that forwards to the JS bar() method. If _any_ JS interop needs to forward from the Dart name renamed to the JS named bar, then _all_ JS interop would have the same rename.
So the renames that work in dart2js are only the static ones.
If I recall correctly, ddc can only handle it when the invocation is typed. When invocations are dynamic (via a dcall) it will not apply the rename either.
I should note that even though the issue is more visible because of dart2js's implementation, there is an actual composition problem here. We allow users to define JS-interop classes without coordination. If two libraries expose the same JS class, they are both allowed and equally usable. Because dart2js dispatches based on the underlying value (in this case JS-instance), it needs to ensure that all JS-interop definitions for that JS class are consistent with each other. Because we want to avoid link-time errors, we enforce this modularly by limiting what the user can do. When we disallow renames (and method bodies among other things), we allow the declarations to be combined without conflict.
If I recall correctly,
ddccan only handle it when the invocation is typed. When invocations are dynamic (via adcall) it will not apply the rename either.
Correct.
Because dart2js dispatches based on the underlying value (in this case JS-instance), it needs to ensure that all JS-interop definitions for that JS class are consistent with each other.
Right, that's why we ideally would like a more statically predictable JS interop. If we can trust the calling side then we can do the rename there. That's what the DDC implementation for instance member renames looked like.
What is the status on this one?
This one issue is a long arc, and wont fit entirely into the September release, so we will be moving it to the October release soon (likely next week? We can move it now if you prefer).
Many checks have landed, and we'll continue to land them before and after the next milestone.
Adding on to what Siggi said, should we continue to move the milestone until everything we want here has been added? We'll likely continue to add things here and stretch this across multiple milestones.
Consider making this an Epic, with sub-issue inside it. So each sub-issue can be place in different milestone. Continue rolling this over to the next milestone defeat the purpose of the milestone.
I agree with making it an Epic. We'll need to break this down into bits that will make sense to include in milestones.