[dart:js_interop] Add ExternalDartReference

Closes https://github.com/dart-lang/sdk/issues/55187

Adds a faster way for users to pass opaque Dart values to
JS without the need for boxing like in JSBoxedDartObject.
This does mean, however, that this new type can't be a JS type,
and therefore cannot have interop members declared on it.
Refactors existing code to handle that distinction.

CoreLibraryReviewExempt: Backend-specific library that's been reviewed by both dart2wasm and JS compiler teams.
Change-Id: Ia86f1fe3476512fc0e5f382e05739713b687f092
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358224
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Srujan Gaddam 2024-03-26 15:43:19 +00:00 committed by Commit Queue
parent bcf64bdfeb
commit 1232260158
18 changed files with 275 additions and 45 deletions

View file

@ -46,10 +46,15 @@ advantage of these improvements, set your package's
- Fixes an issue with several comparison operators in `JSAnyOperatorExtension`
that were declared to return `JSBoolean` but really returned `bool`. This led
to runtime errors when trying to use the return values. The implementation now
returns a `JSBoolean` to align with the interface. See issues [#55024] for
returns a `JSBoolean` to align with the interface. See issue [#55024] for
more details.
- Added `ExternalDartReference` and related conversion functions
`toExternalReference` and `toDartObject`. This is a faster alternative to
`JSBoxedDartObject`, but with fewer safety guarantees and fewer
interoperability capabilities. See [#55187] for more details.
[#55024]: https://github.com/dart-lang/sdk/issues/55024
[#55187]: https://github.com/dart-lang/sdk/issues/55187
### Tools

View file

@ -10698,7 +10698,7 @@ const Template<Message Function(String string2)>
problemMessageTemplate:
r"""External JS interop member contains invalid types in its function signature: '#string2'.""",
correctionMessageTemplate:
r"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
r"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
withArguments:
_withArgumentsJsInteropStaticInteropExternalFunctionTypeViolation,
);
@ -10719,7 +10719,7 @@ Message _withArgumentsJsInteropStaticInteropExternalFunctionTypeViolation(
problemMessage:
"""External JS interop member contains invalid types in its function signature: '${string2}'.""",
correctionMessage:
"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
arguments: {
'string2': string2,
},
@ -10938,7 +10938,7 @@ const Template<Message Function(String string2)>
problemMessageTemplate:
r"""Function converted via 'toJS' contains invalid types in its function signature: '#string2'.""",
correctionMessageTemplate:
r"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
r"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
withArguments: _withArgumentsJsInteropStaticInteropToJSFunctionTypeViolation,
);
@ -10958,7 +10958,7 @@ Message _withArgumentsJsInteropStaticInteropToJSFunctionTypeViolation(
problemMessage:
"""Function converted via 'toJS' contains invalid types in its function signature: '${string2}'.""",
correctionMessage:
"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
arguments: {
'string2': string2,
},

View file

@ -927,6 +927,9 @@ class JsInteropChecks extends RecursiveVisitor {
// If it can be used as a representation type of an interop extension type,
// it is okay to be used on an external member.
if (extensionIndex.isAllowedRepresentationType(type)) return true;
// ExternalDartReference is allowed on interop members even though it's not
// an interop type.
if (extensionIndex.isExternalDartReferenceType(type)) return true;
if (type is InterfaceType) {
final cls = type.classNode;
// Primitive types are okay.
@ -939,8 +942,8 @@ class JsInteropChecks extends RecursiveVisitor {
}
} else if (type is ExtensionType) {
// Extension types that wrap other allowed types are also okay. Interop
// extension types are handled above, so this is essentially for extension
// types on primitives.
// extension types and ExternalDartReference are handled above, so this is
// essentially for extension types on primitives.
return _isAllowedExternalType(type.extensionTypeErasure);
}
return false;

View file

@ -697,6 +697,7 @@ class ExtensionIndex {
final Map<Reference, ExtensionTypeMemberDescriptor>
_extensionTypeMemberIndex = {};
final Map<Reference, Reference> _extensionTypeTearOffIndex = {};
final Set<Reference> _externalDartReferences = {};
final Class? _javaScriptObject;
final Set<Library> _processedExtensionLibraries = {};
final Set<Library> _processedExtensionTypeLibraries = {};
@ -1013,5 +1014,23 @@ class ExtensionIndex {
}
bool isJSType(ExtensionTypeDeclaration decl) =>
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop';
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop' &&
decl.name.startsWith('JS');
bool isExternalDartReference(ExtensionTypeDeclaration decl) =>
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop' &&
decl.name == 'ExternalDartReference';
bool isExternalDartReferenceType(DartType type) {
if (type is ExtensionType) {
final decl = type.extensionTypeDeclaration;
if (_externalDartReferences.contains(decl.reference)) return true;
if (isExternalDartReference(decl) ||
isExternalDartReferenceType(decl.declaredRepresentationType)) {
_externalDartReferences.add(decl.reference);
return true;
}
}
return false;
}
}

View file

@ -1,8 +1,7 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
show ExtensionIndex;
import 'package:kernel/ast.dart';
import 'package:kernel/type_environment.dart';
@ -14,10 +13,9 @@ class CallbackSpecializer {
final StatefulStaticTypeContext _staticTypeContext;
final MethodCollector _methodCollector;
final CoreTypesUtil _util;
final ExtensionIndex _extensionIndex;
CallbackSpecializer(this._staticTypeContext, this._util,
this._methodCollector, this._extensionIndex);
CallbackSpecializer(
this._staticTypeContext, this._util, this._methodCollector);
bool _needsArgumentsLength(FunctionType type) =>
type.requiredParameterCount < type.positionalParameters.length;
@ -33,8 +31,7 @@ class CallbackSpecializer {
DartType callbackParameterType = function.positionalParameters[i];
Expression expression;
VariableGet v = VariableGet(positionalParameters[i]);
if (_extensionIndex.isStaticInteropType(callbackParameterType) &&
boxExternRef) {
if (_util.isJSValueType(callbackParameterType) && boxExternRef) {
expression = _createJSValue(v);
if (!callbackParameterType.isPotentiallyNullable) {
expression = NullCheck(expression);

View file

@ -36,8 +36,8 @@ class InteropTransformer extends Transformer {
InteropTransformer._(this._staticTypeContext, this._util,
this._methodCollector, extensionIndex)
: _callbackSpecializer = CallbackSpecializer(
_staticTypeContext, _util, _methodCollector, extensionIndex),
: _callbackSpecializer =
CallbackSpecializer(_staticTypeContext, _util, _methodCollector),
_inlineExpander =
InlineExpander(_staticTypeContext, _util, _methodCollector),
_interopSpecializerFactory = InteropSpecializerFactory(

View file

@ -68,9 +68,12 @@ class CoreTypesUtil {
wasmExternRefClass.getThisType(coreTypes, Nullability.nullable);
Procedure jsifyTarget(DartType type) =>
_extensionIndex.isStaticInteropType(type)
? jsValueUnboxTarget
: jsifyRawTarget;
isJSValueType(type) ? jsValueUnboxTarget : jsifyRawTarget;
/// Return whether [type] erases to a `JSValue`.
bool isJSValueType(DartType type) =>
_extensionIndex.isStaticInteropType(type) ||
_extensionIndex.isExternalDartReferenceType(type);
void annotateProcedure(
Procedure procedure, String pragmaOptionString, AnnotationType type) {
@ -101,7 +104,7 @@ class CoreTypesUtil {
return invokeOneArg(dartifyRawTarget, invocation);
} else {
Expression expression;
if (_extensionIndex.isStaticInteropType(returnType)) {
if (isJSValueType(returnType)) {
// TODO(joshualitt): Expose boxed `JSNull` and `JSUndefined` to Dart
// code after migrating existing users of js interop on Dart2Wasm.
// expression = _createJSValue(invocation);

View file

@ -4723,7 +4723,7 @@ const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
problemMessageTemplate:
r"""External JS interop member contains an invalid type: '#type'.""",
correctionMessageTemplate:
r"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
r"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
withArguments:
_withArgumentsJsInteropStaticInteropExternalAccessorTypeViolation,
);
@ -4747,7 +4747,7 @@ Message _withArgumentsJsInteropStaticInteropExternalAccessorTypeViolation(
"""External JS interop member contains an invalid type: '${type}'.""" +
labeler.originMessages,
correctionMessage:
"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
arguments: {
'type': _type,
},

View file

@ -5932,15 +5932,15 @@ JsInteropNonStaticWithStaticInteropSupertype:
# to avoid duplication?
JsInteropStaticInteropExternalAccessorTypeViolation:
problemMessage: "External JS interop member contains an invalid type: '#type'."
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
JsInteropStaticInteropExternalFunctionTypeViolation:
problemMessage: "External JS interop member contains invalid types in its function signature: '#string2'."
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
JsInteropStaticInteropToJSFunctionTypeViolation:
problemMessage: "Function converted via 'toJS' contains invalid types in its function signature: '#string2'."
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
JsInteropStaticInteropGenerativeConstructor:
problemMessage: "`@staticInterop` classes should not contain any generative constructors."

View file

@ -54,6 +54,7 @@ erase
exhaustively
exportable
extensiontype
externaldartreference
f
ffi
finality
@ -84,8 +85,8 @@ loadlibrary
macro
member(s)
migrate
modifier
mocking
modifier
n
name.#name
name.stack

View file

@ -118,6 +118,22 @@ extension ObjectToJSBoxedDartObject on Object {
}
}
/// [ExternalDartReference] <-> [Object]
@patch
extension ExternalDartReferenceToObject on ExternalDartReference {
@patch
@pragma('dart2js:prefer-inline')
Object get toDartObject => this;
}
@patch
extension ObjectToExternalDartReference on Object {
@patch
@pragma('dart2js:prefer-inline')
ExternalDartReference get toExternalReference =>
this as ExternalDartReference;
}
/// [JSPromise] -> [Future].
@patch
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {

View file

@ -64,7 +64,9 @@ typedef JSSymbolRepType = interceptors.JavaScriptSymbol;
typedef JSBigIntRepType = interceptors.JavaScriptBigInt;
/// [JSVoid] is just a typedef for [void]. While we could just use
/// `JSUndefined`, in the future we may be able to use this to elide `return`s
/// in JS trampolines.
// While this type is not a JS type, it is here for convenience so we don't need
// to create a new shared library.
typedef ExternalDartReferenceRepType = Object;
// JSVoid is just a typedef for void.
typedef JSVoidRepType = void;

View file

@ -136,6 +136,21 @@ extension ObjectToJSBoxedDartObject on Object {
}
}
/// [ExternalDartReference] <-> [Object]
@patch
extension ExternalDartReferenceToObject on ExternalDartReference {
@patch
Object get toDartObject =>
jsObjectToDartObject((this as JSValue).toExternRef);
}
@patch
extension ObjectToExternalDartReference on Object {
@patch
ExternalDartReference get toExternalReference =>
_boxNonNullable<ExternalDartReference>(jsObjectFromDartObject(this));
}
/// [JSPromise] -> [Future].
@patch
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {

View file

@ -75,7 +75,10 @@ typedef JSSymbolRepType = js.JSValue;
typedef JSBigIntRepType = js.JSValue;
/// [JSVoid] is just a typedef for [void]. While we could just use
/// `JSUndefined`, in the future we may be able to use this to elide `return`s
/// in JS trampolines.
// While this type is not a JS type, it is here for convenience so we don't need
// to create a new shared library.
typedef ExternalDartReferenceRepType = js.JSValue;
// JSVoid is just a typedef for void. While we could just use JSUndefined, in
// the future we may be able to use this to elide `return`s in JS trampolines.
typedef JSVoidRepType = void;

View file

@ -26,6 +26,9 @@
/// > The runtime types differ based on the backend, so it is important to rely
/// > on static functionality like the conversion functions, for example `toJS`
/// > and not runtime mechanisms like type checks (`is`) and casts (`as`).
/// > Similarly, `identical` may return different results for the same JS value
/// > depending on the compiler. Use `==` to check for equality of two JS types
/// > instead.
///
/// {@category Web}
library;
@ -173,6 +176,10 @@ extension type JSPromise<T extends JSAny?>._(JSPromiseRepType _jsPromise)
/// A Dart object that is wrapped with a JavaScript object so that it can be
/// passed to JavaScript safely.
///
/// Unlike [ExternalDartReference], this can be used as a JS type and is a
/// subtype of [JSAny]. Users can also declare interop types using this as the
/// representation type or declare interop members on this type.
///
/// Use this interface when you want to pass Dart objects within the same
/// runtime through JavaScript. There are no usable members in the resulting
/// [JSBoxedDartObject].
@ -260,6 +267,33 @@ extension type JSSymbol._(JSSymbolRepType _jsSymbol) implements JSAny {}
/// A JavaScript `BigInt`.
extension type JSBigInt._(JSBigIntRepType _jsBigInt) implements JSAny {}
/// An opaque reference to a Dart object that can be passed to JavaScript.
///
/// The reference representation depends on the underlying platform. When
/// compiling to JavaScript, a Dart object is a JavaScript object, and can be
/// used directly without any conversions. When compiling to Wasm, an internal
/// Wasm function is used to convert the Dart object to an opaque JavaScript
/// value, which can later be converted back using another internal function.
///
/// This interface is a faster alternative to [JSBoxedDartObject] by not
/// wrapping the Dart object with a JavaScript object. However, unlike
/// [JSBoxedDartObject], this value belongs to the Dart runtime, and therefore
/// can not be used as a JS type. This means users cannot declare interop types
/// using this as the representation type or declare interop members on this
/// type. This type is also not a subtype of [JSAny]. This type can only be used
/// as parameter and return types of external JavaScript interop members or
/// callbacks. Use [JSBoxedDartObject] to avoid those limitations.
///
/// Besides these differences, [ExternalDartReference] operates functionally the
/// same as [JSBoxedDartObject]. Use it to pass Dart objects within the same
/// runtime through JavaScript. There are no usable members in the resulting
/// [ExternalDartReference].
///
/// See [ObjectToExternalDartReference.toExternalReference] to allow an
/// arbitrary [Object] to be passed to JavaScript.
extension type ExternalDartReference._(
ExternalDartReferenceRepType _externalDartReference) implements Object {}
/// JS type equivalent for `undefined` for interop member return types.
///
/// Prefer using `void` instead of this.
@ -459,7 +493,7 @@ extension FunctionToJSExportedDartFunction on Function {
/// Conversions from [JSBoxedDartObject] to [Object].
extension JSBoxedDartObjectToObject on JSBoxedDartObject {
/// The Dart [Object] that this [JSBoxedDartObjectToObject] wrapped.
/// The Dart [Object] that this [JSBoxedDartObject] wrapped.
///
/// Throws an [Exception] if the Dart runtime was not the same as the one in
/// which the [Object] was wrapped or if this was not a wrapped Dart [Object].
@ -473,9 +507,48 @@ extension ObjectToJSBoxedDartObject on Object {
/// There are no usable members in the resulting [JSBoxedDartObject] and you
/// may get a new [JSBoxedDartObject] when calling [toJSBox] on the same Dart
/// [Object].
///
/// Throws an [Exception] if this [Object] is a JavaScript value.
///
/// Unlike [ObjectToExternalDartReference.toExternalReference], this returns a
/// JavaScript value. Therefore, the representation is guaranteed to be
/// consistent across all platforms and interop members can be declared on
/// [JSBoxedDartObject]s.
external JSBoxedDartObject get toJSBox;
}
/// Conversions from [ExternalDartReference] to [Object].
extension ExternalDartReferenceToObject on ExternalDartReference {
/// The Dart [Object] that this [ExternalDartReference] refers to.
///
/// When compiling to JavaScript, a Dart object is a JavaScript object, and
/// therefore this directly returns the Dart object. When compiling to Wasm,
/// an internal Wasm function is used to convert the opaque JavaScript value
/// to the original Dart object.
external Object get toDartObject;
}
/// Conversions from [Object] to [ExternalDartReference].
extension ObjectToExternalDartReference on Object {
/// An opaque reference to this [Object] which can be passed to JavaScript.
///
/// When compiling to JavaScript, a Dart object is a JavaScript object, and
/// therefore this directly returns the Dart object. When compiling to Wasm,
/// an internal Wasm function is used to convert the Dart object to an opaque
/// JavaScript value.
///
/// A value of type [ExternalDartReference] should be treated as completely
/// opaque. It can only be passed around as-is or converted back using
/// [ExternalDartReferenceToObject.toDartObject].
///
/// When this getter is called multiple times on the same Dart object, the
/// underlying references in the resulting [ExternalDartReference]s are
/// guaranteed to be equal. Therefore, `==` will always return true between
/// such [ExternalDartReference]s. However, like JS types, `identical` between
/// such values may return different results depending on the compiler.
external ExternalDartReference get toExternalReference;
}
/// Conversions from [JSPromise] to [Future].
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
/// A [Future] that either completes with the result of the resolved

View file

@ -99,3 +99,14 @@ extension type ENonInterop._(EObject _) {
// ^
// [web] Extension type member is marked 'external', but the representation type of its extension type is not a valid JS interop type.
}
extension type EExternalDartReference._(ExternalDartReference _) {
external EExternalDartReference();
// ^
// [web] Extension type member is marked 'external', but the representation type of its extension type is not a valid JS interop type.
}
@JS()
extension type EExternalDartReference2._(ExternalDartReference _) {}
// ^
// [web] Extension type 'EExternalDartReference2' is marked with a '@JS' annotation, but its representation type is not a valid JS interop type: 'ExternalDartReference'.

View file

@ -0,0 +1,66 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Requirements=checked-implicit-downcasts
import 'dart:js_interop';
import 'package:expect/expect.dart';
const isJSBackend = const bool.fromEnvironment('dart.library.html');
extension type EExternalDartReference(ExternalDartReference _)
implements ExternalDartReference {}
@JS()
external ExternalDartReference externalDartReference;
@JS()
external EExternalDartReference eExternalDartReference;
@JS('externalDartReference')
external ExternalDartReference? nullableExternalDartReference;
class DartClass {
int field;
DartClass(this.field);
}
void main() {
final dartObject = DartClass(42);
externalDartReference = dartObject.toExternalReference;
Expect.equals(dartObject, externalDartReference.toDartObject as DartClass);
Expect.isTrue(
identical(dartObject, externalDartReference.toDartObject as DartClass));
eExternalDartReference = EExternalDartReference(externalDartReference);
Expect.equals(dartObject, eExternalDartReference.toDartObject as DartClass);
Expect.isTrue(
identical(dartObject, eExternalDartReference.toDartObject as DartClass));
// Multiple invocations should return the same underlying value, which is
// tested by `==`.
Expect.equals(externalDartReference, dartObject.toExternalReference);
// However, they may or may not be identical depending on the compiler due to
// dart2wasm wrapping values with new JSValue instances.
if (isJSBackend) {
Expect.isTrue(
identical(externalDartReference, dartObject.toExternalReference));
} else {
Expect.isFalse(
identical(externalDartReference, dartObject.toExternalReference));
}
final jsString = ''.toJS;
// We don't validate that the input is a Dart object or a JS value as that may
// be expensive to validate. We end up externalizing the JSValue wrapper in
// this case.
Expect.equals(jsString.toExternalReference.toDartObject, jsString);
Expect.isTrue(identical(jsString.toExternalReference.toDartObject, jsString));
// Check that we do the right thing with nullability still.
nullableExternalDartReference = null;
if (hasSoundNullSafety) Expect.throws(() => externalDartReference);
}

View file

@ -2,15 +2,17 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// dart2jsOptions=-O2
/// Requirements=checked-implicit-downcasts
// Test that Function.toJS properly converts/casts arguments and return values
// when using Dart types.
// when using non-JS types.
import 'dart:js_interop';
import 'package:expect/expect.dart';
import 'package:expect/minitest.dart';
const isJSBackend = const bool.fromEnvironment('dart.library.html');
@JS()
external void eval(String code);
@ -32,6 +34,9 @@ external void voidF(JSString s);
@JS('jsFunction')
external JSAny? anyF([JSAny? n]);
@JS('jsFunction')
external ExternalDartReference externalDartReferenceF(ExternalDartReference b);
@JS()
external JSAny? callFunctionWithUndefined();
@ -71,6 +76,23 @@ void main() {
jsFunction = ((String arg) => arg).toJS;
voidF('voidF'.toJS);
// Test ExternalDartReference.
final set = {};
jsFunction = ((ExternalDartReference arg) => arg).toJS;
expect(externalDartReferenceF(set.toExternalReference).toDartObject, set);
// This doesn't fail for the same reason JS types don't fail - the value gets
// boxed.
anyF(''.toJS);
// However, if we try to internalize it to the wrong value, that should fail.
jsFunction = ((ExternalDartReference arg) {
arg.toDartObject as Set;
}).toJS;
// TODO(srujzs): On dart2wasm, this is a `RuntimeError: illegal cast` because
// of the call to `internalize`. Is there any way to first check that the
// value can be internalized and throw if not? Would that slow down the
// round-trip? Most likely, so just check the JS compilers for now.
if (isJSBackend) Expect.throws(() => anyF(''.toJS));
// Test nullability with JS null and JS undefined.
eval('''
globalThis.callFunctionWithUndefined = function() {
@ -135,9 +157,7 @@ void main() {
final any = anyF(empty);
// TODO(54179): Better way of writing this is to cast to JSNumber and
// convert, but currently that does not throw on dart2wasm.
if (!any.typeofEquals('number')) {
throw TypeError();
}
if (!any.isA<JSNumber>()) throw TypeError();
});
expect(anyF(zero), zero);
@ -155,9 +175,7 @@ void main() {
final any = anyF(empty);
// TODO(54179): Better way of writing this is to cast to JSNumber and
// convert, but currently that does not throw on dart2wasm.
if (!any.typeofEquals('number')) {
throw TypeError();
}
if (!any.isA<JSNumber>()) throw TypeError();
});
expect(anyF(zero), zero);
@ -172,9 +190,7 @@ void main() {
final any = anyF(empty);
// TODO(54179): Better way of writing this is to cast to JSNumber and
// convert, but currently that does not throw on dart2wasm.
if (!any.typeofEquals('number')) {
throw TypeError();
}
if (!any.isA<JSNumber>()) throw TypeError();
});
expect(anyF(zero), zero);
}