-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[dart:js_interop] Add external isA
and asA
helpers to dart:js_interop for is
and as
equivalents
#54138
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
Interesting! Here's a random drive-by comment:
Aha, so this is a description of a transformation which is applied to each call site for Otherwise, it sounds like it would depend on having information which isn't there: Extension types (interop or not) are not expected to exist at run time. I would actually expect them to be eliminated already in the kernel code that any given backend receives. But if it is a transformation on each call site (and it occurs at a time where extension types are still present) then it sounds very much like the treatment which is given to every invocation of an I wonder if this idea (that is: use a marker like |
Indeed. Our JS interop transformations in general occur at the call-site (which is why we don't let users do tear-offs of external members) like you've noted, and this would be the same. We do this with a few other methods as well, like
Do you mean outside of interop? In general, I can see it useful for functions that need transformations, but I don't think this is unique to extension type members. |
If you want to lift that restriction then a regular Dart function literal whose body performs the magic interop invocation should be sufficient (there is no special magic about the behavior of
Right, and we probably don't want to use All kinds of invocations could be compiled in a customized manner if the callee declaration carries a marker which is interpreted to be a request for this kind of customized translation. (Basically, it's a macro which is applied at all call sites). For instance, invocations of const provideMissingArgumentsFromReceiver = 0; // Cheat.
class Data {
final int x, y;
Data({required this.x, required this.y});
@provideMissingArgumentsFromReceiver
Data copyWith({required x, required y}) => Data(x: x, y: y);
}
void main() {
var d = Data(x: 3, y: 4);
var d2 = d.copyWith(x: 2); // OK, because of a compiler transformation.
} The expression The compiler would not report an error for the missing argument named |
Agreed! We recommend users create a function literal instead, but we can likely do that ourselves for users. We maybe need to be a bit careful about accounting for the CFE's generated calls and making sure tear-off equality is retained when called on the same object, but it's doable. For the example above with |
Related issue: #52030
Users are used to using
is
andas
fordart:html
objects. This allowed them to write simple code likex is Window
, and know thatx
actually is a JSWindow
object. Replicating this on some arbitrary objectx
(let's say it's typed asJSAny?
) requires the following withdart:js_interop
:which is definitely an ergonomic downgrade. Even if we move
instanceOfString
up toJSAny?
(which may be worthwhile), it's still less ideal to have to type out the name of the type. And if we had more complex types, like user-defined JS interop types that exist in a particular library, that String gets more annoying to write.One option is transformations to handle this. Consider a method
external bool isA<T>()
that exists onJSAny?
. This method will do the following (assume thatdart:js_interop
types are extension types, which they will be):T
is an extension interop type or a@staticInterop
type.T
is an extension type that is one of thedart:js_interop
types or any number of extension types on them, do the following:dart:js_interop
type is one of the non-JSObject
types, lower to the equivalenttypeofEquals
check e.g.isA<JSString>()
->typeofEquals('string')
. We might want to be a little careful here of extension types that may wrap these types e.g.extension type S(JSString s)
. I'm inclined to say that we should still lower to the same check here and ignore any renaming users may do.@JS()
renaming annotations (including the library's) if any to determine the type users want and then do aninstanceofString
check using that type e.g.isA<JSArray>
->instanceofString('Array')
.T
is@staticInterop
type, this is the same as the second bullet point of step 2.This would make the check now look like:
T asA<T>()
could just be implemented as:We might not want an
asA
because users may think they need it after anisA
, which they don't. However, it is useful for users who want stronger checking and if we can markisA
as a function whose value doesn't change (handwaving dart2js optimizations here), then maybe we can optimize the duplicateisA
.Note that this doesn't give us type promotion still, but that wouldn't be possible without some language functionality anyways.
cc @kevmoo because migrating without this is admittedly less ergonomic when working with
package:web
types.A potential wrinkle here is the treatment of types that have object literal constructors e.g.
If we wrote
isA<ObjectLiteral>(x)
, what are we asking for here? The above algorithm would try do ax instanceof globalContext.ObjectLiteral
, but that's likely not what the user wants. We could make this check equivalent to atypeof x == 'object'
to better capture the fact that we're using it as an object literal, but this might not be what the user wants either. After all, there might actually exist aglobalContext.ObjectLiteral
that the user wants to check here. Should we make this an error? Maybe, but that's a loss of expressivity for users.The text was updated successfully, but these errors were encountered: