Description
Related issue: #52030
Users are used to using is
and as
for dart:html
objects. This allowed them to write simple code like x is Window
, and know that x
actually is a JS Window
object. Replicating this on some arbitrary object x
(let's say it's typed as JSAny?
) requires the following with dart:js_interop
:
x.typeofEquals('object') && (x as JSObject).instanceOfString('Window')
which is definitely an ergonomic downgrade. Even if we move instanceOfString
up to JSAny?
(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 on JSAny?
. This method will do the following (assume that dart:js_interop
types are extension types, which they will be):
- Validate that
T
is an extension interop type or a@staticInterop
type. - If
T
is an extension type that is one of thedart:js_interop
types or any number of extension types on them, do the following:
- If the
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. - If it is, then use the
@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')
.
- If
T
is@staticInterop
type, this is the same as the second bullet point of step 2.
This would make the check now look like:
x.isA<Window>()
T asA<T>()
could just be implemented as:
if (!this.isA<T>) throw TypeError(...);
return this as T;
We might not want an asA
because users may think they need it after an isA
, which they don't. However, it is useful for users who want stronger checking and if we can mark isA
as a function whose value doesn't change (handwaving dart2js optimizations here), then maybe we can optimize the duplicate isA
.
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.
- Addendum (1/4/24):
A potential wrinkle here is the treatment of types that have object literal constructors e.g.
extension type ObjectLiteral(JSObject _) {
external ObjectLiteral({JSAny foo});
}
If we wrote isA<ObjectLiteral>(x)
, what are we asking for here? The above algorithm would try do a x instanceof globalContext.ObjectLiteral
, but that's likely not what the user wants. We could make this check equivalent to a typeof 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 a globalContext.ObjectLiteral
that the user wants to check here. Should we make this an error? Maybe, but that's a loss of expressivity for users.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status