1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-03 00:08:46 +00:00

[dart:js_interop] Make ExternalDartReference generic

Closes https://github.com/dart-lang/sdk/issues/55342
Closes https://github.com/dart-lang/sdk/issues/55536
Closes https://github.com/dart-lang/sdk/issues/56015

- Adds a type parameter T that extends Object? to
ExternalDartReference to capture the type of the value
that was externalized.
-- In the JS compilers, the representation type of
ExternalDartReference is now T.
-- In dart2wasm, the representation type is now JSValue?.
- ExternalDartReference no longer implements Object.
- Return type of toDartObject is now T.
- ObjectToExternalDartReference and
ExternalDartReferenceToObject both now are on a T that is
bound to Object?.
- Internal patches for WeakReference and Finalizer are
updated.

Change-Id: Ic2dc834b17ec6a4eb2122cba3c495a6e0a1eae6e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370663
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
This commit is contained in:
Srujan Gaddam 2024-06-26 21:53:53 +00:00 committed by Commit Queue
parent 2653075ac8
commit b8402f5c27
11 changed files with 303 additions and 137 deletions

View File

@ -57,9 +57,18 @@
of a `String` to support other JS values as well, like `TrustedScriptURL`s.
- **Breaking Change** [#55267][]: `isTruthy` and `not` now return `JSBoolean`
instead of `bool` to be consistent with the other operators.
- **Breaking Change** `ExternalDartReference` no longer implements `Object`.
`ExternalDartReference` now accepts a type parameter `T` with a bound of
`Object?` to capture the type of the Dart object that is externalized.
`ExternalDartReferenceToObject.toDartObject` now returns a `T`.
`ExternalDartReferenceToObject` and `ObjectToExternalDartReference` are now
extensions on `T` and `ExternalDartReference<T>`, respectively, where `T
extends Object?`. See [#55342][] and [#55536][] for more details.
[#55508]: https://github.com/dart-lang/sdk/issues/55508
[#55267]: https://github.com/dart-lang/sdk/issues/55267
[#55342]: https://github.com/dart-lang/sdk/issues/55342
[#55536]: https://github.com/dart-lang/sdk/issues/55536
### Tools

View File

@ -33,7 +33,7 @@ class CallbackSpecializer {
VariableGet v = VariableGet(positionalParameters[i]);
if (_util.isJSValueType(callbackParameterType) && boxExternRef) {
expression = _createJSValue(v);
if (!callbackParameterType.isPotentiallyNullable) {
if (!callbackParameterType.extensionTypeErasure.isPotentiallyNullable) {
expression = NullCheck(expression);
}
} else {

View File

@ -70,7 +70,7 @@ class CoreTypesUtil {
Procedure jsifyTarget(DartType type) =>
isJSValueType(type) ? jsValueUnboxTarget : jsifyRawTarget;
/// Return whether [type] erases to a `JSValue`.
/// Whether [type] erases to a `JSValue` or `JSValue?`.
bool isJSValueType(DartType type) =>
_extensionIndex.isStaticInteropType(type) ||
_extensionIndex.isExternalDartReferenceType(type);
@ -112,7 +112,7 @@ class CoreTypesUtil {
// there are static interop types that are not boxed as JSValue, we
// might need a proper cast then.
expression = invokeOneArg(jsValueBoxTarget, invocation);
if (returnType.isPotentiallyNonNullable) {
if (returnType.extensionTypeErasure.isPotentiallyNonNullable) {
expression = NullCheck(expression);
}
} else {

View File

@ -19,7 +19,7 @@ JSObjectRepType _createObjectLiteral() =>
@pragma('dart2js:prefer-inline')
JSObject get globalContext => staticInteropGlobalContext as JSObject;
/// Helper for working with the [JSAny?] top type in a backend agnostic way.
// Helper for working with the JSAny? top type in a backend agnostic way.
@patch
extension NullableUndefineableJSAnyExtension on JSAny? {
@patch
@ -53,7 +53,7 @@ extension JSAnyUtilityExtension on JSAny? {
Object? dartify() => js_util.dartify(this);
}
/// Utility extensions for [Object?].
// Utility extensions for Object?.
@patch
extension NullableObjectUtilExtension on Object? {
@patch
@ -61,7 +61,8 @@ extension NullableObjectUtilExtension on Object? {
JSAny? jsify() => js_util.jsify(this);
}
/// [JSExportedDartFunction] <-> [Function]
// -----------------------------------------------------------------------------
// JSExportedDartFunction <-> Function
@patch
extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
// TODO(srujzs): We should unwrap rather than allow arbitrary JS functions
@ -79,15 +80,16 @@ extension FunctionToJSExportedDartFunction on Function {
js_util.allowInterop(this) as JSExportedDartFunction;
}
/// Embedded global property for wrapped Dart objects passed via JS interop.
///
/// This is a Symbol so that different Dart applications don't share Dart
/// objects from different Dart runtimes. We expect all [JSBoxedDartObject]s to
/// have this Symbol.
// Embedded global property for wrapped Dart objects passed via JS interop.
//
// This is a Symbol so that different Dart applications don't share Dart
// objects from different Dart runtimes. We expect all JSBoxedDartObjects to
// have this Symbol.
final Object _jsBoxedDartObjectProperty =
foreign_helper.JS('', 'Symbol("jsBoxedDartObjectProperty")');
/// [JSBoxedDartObject] <-> [Object]
// -----------------------------------------------------------------------------
// JSBoxedDartObject <-> Object
@patch
extension JSBoxedDartObjectToObject on JSBoxedDartObject {
@patch
@ -118,23 +120,25 @@ extension ObjectToJSBoxedDartObject on Object {
}
}
/// [ExternalDartReference] <-> [Object]
// -----------------------------------------------------------------------------
// ExternalDartReference <-> T
@patch
extension ExternalDartReferenceToObject on ExternalDartReference {
extension ExternalDartReferenceToObject<T extends Object?>
on ExternalDartReference<T> {
@patch
@pragma('dart2js:prefer-inline')
Object get toDartObject => this;
T get toDartObject => _externalDartReference;
}
@patch
extension ObjectToExternalDartReference on Object {
extension ObjectToExternalDartReference<T extends Object?> on T {
@patch
@pragma('dart2js:prefer-inline')
ExternalDartReference get toExternalReference =>
ExternalDartReference._(this);
ExternalDartReference<T> get toExternalReference =>
ExternalDartReference<T>._(this);
}
/// [JSPromise] -> [Future].
// JSPromise -> Future
@patch
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
@patch
@ -142,7 +146,8 @@ extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
Future<T> get toDart => js_util.promiseToFuture<T>(this);
}
/// [JSArrayBuffer] <-> [ByteBuffer]
// -----------------------------------------------------------------------------
// JSArrayBuffer <-> ByteBuffer
@patch
extension JSArrayBufferToByteBuffer on JSArrayBuffer {
@patch
@ -157,7 +162,8 @@ extension ByteBufferToJSArrayBuffer on ByteBuffer {
JSArrayBuffer get toJS => this as JSArrayBuffer;
}
/// [JSDataView] <-> [ByteData]
// -----------------------------------------------------------------------------
// JSDataView <-> ByteData
@patch
extension JSDataViewToByteData on JSDataView {
@patch
@ -172,7 +178,8 @@ extension ByteDataToJSDataView on ByteData {
JSDataView get toJS => this as JSDataView;
}
/// [JSInt8Array] <-> [Int8List]
// -----------------------------------------------------------------------------
// JSInt8Array <-> Int8List
@patch
extension JSInt8ArrayToInt8List on JSInt8Array {
@patch
@ -187,7 +194,8 @@ extension Int8ListToJSInt8Array on Int8List {
JSInt8Array get toJS => this as JSInt8Array;
}
/// [JSUint8Array] <-> [Uint8List]
// -----------------------------------------------------------------------------
// JSUint8Array <-> Uint8List
@patch
extension JSUint8ArrayToUint8List on JSUint8Array {
@patch
@ -202,7 +210,8 @@ extension Uint8ListToJSUint8Array on Uint8List {
JSUint8Array get toJS => this as JSUint8Array;
}
/// [JSUint8ClampedArray] <-> [Uint8ClampedList]
// -----------------------------------------------------------------------------
// JSUint8ClampedArray <-> Uint8ClampedList
@patch
extension JSUint8ClampedArrayToUint8ClampedList on JSUint8ClampedArray {
@patch
@ -217,7 +226,8 @@ extension Uint8ClampedListToJSUint8ClampedArray on Uint8ClampedList {
JSUint8ClampedArray get toJS => this as JSUint8ClampedArray;
}
/// [JSInt16Array] <-> [Int16List]
// -----------------------------------------------------------------------------
// JSInt16Array <-> Int16List
@patch
extension JSInt16ArrayToInt16List on JSInt16Array {
@patch
@ -232,7 +242,8 @@ extension Int16ListToJSInt16Array on Int16List {
JSInt16Array get toJS => this as JSInt16Array;
}
/// [JSUint16Array] <-> [Uint16List]
// -----------------------------------------------------------------------------
// JSUint16Array <-> Uint16List
@patch
extension JSUint16ArrayToInt16List on JSUint16Array {
@patch
@ -247,7 +258,8 @@ extension Uint16ListToJSInt16Array on Uint16List {
JSUint16Array get toJS => this as JSUint16Array;
}
/// [JSInt32Array] <-> [Int32List]
// -----------------------------------------------------------------------------
// JSInt32Array <-> Int32List
@patch
extension JSInt32ArrayToInt32List on JSInt32Array {
@patch
@ -262,7 +274,8 @@ extension Int32ListToJSInt32Array on Int32List {
JSInt32Array get toJS => this as JSInt32Array;
}
/// [JSUint32Array] <-> [Uint32List]
// -----------------------------------------------------------------------------
// JSUint32Array <-> Uint32List
@patch
extension JSUint32ArrayToUint32List on JSUint32Array {
@patch
@ -277,7 +290,8 @@ extension Uint32ListToJSUint32Array on Uint32List {
JSUint32Array get toJS => this as JSUint32Array;
}
/// [JSFloat32Array] <-> [Float32List]
// -----------------------------------------------------------------------------
// JSFloat32Array <-> Float32List
@patch
extension JSFloat32ArrayToFloat32List on JSFloat32Array {
@patch
@ -292,7 +306,8 @@ extension Float32ListToJSFloat32Array on Float32List {
JSFloat32Array get toJS => this as JSFloat32Array;
}
/// [JSFloat64Array] <-> [Float64List]
// -----------------------------------------------------------------------------
// JSFloat64Array <-> Float64List
@patch
extension JSFloat64ArrayToFloat64List on JSFloat64Array {
@patch
@ -307,7 +322,8 @@ extension Float64ListToJSFloat64Array on Float64List {
JSFloat64Array get toJS => this as JSFloat64Array;
}
/// [JSArray] <-> [List]
// -----------------------------------------------------------------------------
// JSArray <-> List
@patch
extension JSArrayToList<T extends JSAny?> on JSArray<T> {
@patch
@ -339,7 +355,8 @@ extension ListToJSArray<T extends JSAny?> on List<T> {
JSArray<T> get toJSProxyOrRef => this as JSArray<T>;
}
/// [JSNumber] -> [double] or [int].
// -----------------------------------------------------------------------------
// JSNumber -> double or int
@patch
extension JSNumberToNumber on JSNumber {
@patch
@ -351,7 +368,8 @@ extension JSNumberToNumber on JSNumber {
int get toDartInt => this as int;
}
/// [double] -> [JSNumber].
// -----------------------------------------------------------------------------
// double -> JSNumber
@patch
extension DoubleToJSNumber on double {
@patch
@ -359,7 +377,8 @@ extension DoubleToJSNumber on double {
JSNumber get toJS => JSNumber._(this);
}
/// [JSBoolean] <-> [bool]
// -----------------------------------------------------------------------------
// JSBoolean <-> bool
@patch
extension JSBooleanToBool on JSBoolean {
@patch
@ -374,7 +393,8 @@ extension BoolToJSBoolean on bool {
JSBoolean get toJS => JSBoolean._(this);
}
/// [JSString] <-> [String]
// -----------------------------------------------------------------------------
// JSString <-> String
@patch
extension JSStringToString on JSString {
@patch

View File

@ -66,7 +66,7 @@ typedef JSBigIntRepType = interceptors.JavaScriptBigInt;
// 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;
typedef ExternalDartReferenceRepType<T> = T;
// JSVoid is just a typedef for void.
typedef JSVoidRepType = void;

View File

@ -22,7 +22,7 @@ js_types.JSObjectRepType _createObjectLiteral() =>
@patch
JSObject get globalContext => js_util.globalThis as JSObject;
/// Helper for working with the [JSAny?] top type in a backend agnostic way.
// Helper for working with the JSAny? top type in a backend agnostic way.
@patch
extension NullableUndefineableJSAnyExtension on JSAny? {
// TODO(joshualitt): To support incremental migration of existing users to
@ -64,14 +64,15 @@ extension JSAnyUtilityExtension on JSAny? {
Object? dartify() => js_util.dartify(this);
}
/// Utility extensions for [Object?].
// Utility extensions for Object?.
@patch
extension NullableObjectUtilExtension on Object? {
@patch
JSAny? jsify() => js_util.jsify(this);
}
/// [JSExportedDartFunction] <-> [Function]
// -----------------------------------------------------------------------------
// JSExportedDartFunction <-> Function
@patch
extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
@patch
@ -93,15 +94,16 @@ extension FunctionToJSExportedDartFunction on Function {
'transformed by the interop transformer.');
}
/// Embedded global property for wrapped Dart objects passed via JS interop.
///
/// This is a Symbol so that different Dart applications don't share Dart
/// objects from different Dart runtimes. We expect all [JSBoxedDartObject]s to
/// have this Symbol.
// Embedded global property for wrapped Dart objects passed via JS interop.
//
// This is a Symbol so that different Dart applications don't share Dart
// objects from different Dart runtimes. We expect all JSBoxedDartObjects to
// have this Symbol.
final JSSymbol _jsBoxedDartObjectProperty = JSSymbol._(JSValue(
js_helper.JS<WasmExternRef?>('() => Symbol("jsBoxedDartObjectProperty")')));
/// [JSBoxedDartObject] <-> [Object]
// -----------------------------------------------------------------------------
// JSBoxedDartObject <-> Object
@patch
extension JSBoxedDartObjectToObject on JSBoxedDartObject {
@patch
@ -130,22 +132,36 @@ extension ObjectToJSBoxedDartObject on Object {
}
}
/// [ExternalDartReference] <-> [Object]
// -----------------------------------------------------------------------------
// ExternalDartReference <-> Object
@patch
extension ExternalDartReferenceToObject on ExternalDartReference {
extension ExternalDartReferenceToObject<T extends Object?>
on ExternalDartReference<T> {
@patch
Object get toDartObject =>
jsObjectToDartObject(this._externalDartReference.toExternRef);
T get toDartObject {
// TODO(srujzs): We could do an `unsafeCast` here for performance, but
// that can result in unsoundness for users. Alternatively, we can
// introduce a generic version of `JSValue` which would allow us to safely
// `unsafeCast`. However, this has its own issues where a user can't
// do casts like `ExternalDartReference<Object> as
// ExternalDartReference<int>` since `JSValue<Object>` is not a subtype of
// `JSValue<int>`, even though it may be valid to do such a cast.
final t = this._externalDartReference;
return (t == null ? null : jsObjectToDartObject(t.toExternRef)) as T;
}
}
@patch
extension ObjectToExternalDartReference on Object {
extension ObjectToExternalDartReference<T extends Object?> on T {
@patch
ExternalDartReference get toExternalReference =>
ExternalDartReference._(JSValue(jsObjectFromDartObject(this)));
ExternalDartReference<T> get toExternalReference {
final t = this;
return ExternalDartReference<T>._(
t == null ? null : JSValue(jsObjectFromDartObject(t)));
}
}
/// [JSPromise] -> [Future].
// JSPromise -> Future
@patch
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
@patch
@ -182,7 +198,8 @@ extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
}
}
/// [JSArrayBuffer] <-> [ByteBuffer]
// -----------------------------------------------------------------------------
// JSArrayBuffer <-> ByteBuffer
@patch
extension JSArrayBufferToByteBuffer on JSArrayBuffer {
@patch
@ -202,7 +219,8 @@ extension ByteBufferToJSArrayBuffer on ByteBuffer {
}
}
/// [JSDataView] <-> [ByteData]
// -----------------------------------------------------------------------------
// JSDataView <-> ByteData
@patch
extension JSDataViewToByteData on JSDataView {
@patch
@ -220,7 +238,8 @@ extension ByteDataToJSDataView on ByteData {
}
}
/// [JSInt8Array] <-> [Int8List]
// -----------------------------------------------------------------------------
// JSInt8Array <-> Int8List
@patch
extension JSInt8ArrayToInt8List on JSInt8Array {
@patch
@ -238,7 +257,8 @@ extension Int8ListToJSInt8Array on Int8List {
}
}
/// [JSUint8Array] <-> [Uint8List]
// -----------------------------------------------------------------------------
// JSUint8Array <-> Uint8List
@patch
extension JSUint8ArrayToUint8List on JSUint8Array {
@patch
@ -256,7 +276,8 @@ extension Uint8ListToJSUint8Array on Uint8List {
}
}
/// [JSUint8ClampedArray] <-> [Uint8ClampedList]
// -----------------------------------------------------------------------------
// JSUint8ClampedArray <-> Uint8ClampedList
@patch
extension JSUint8ClampedArrayToUint8ClampedList on JSUint8ClampedArray {
@patch
@ -275,7 +296,8 @@ extension Uint8ClampedListToJSUint8ClampedArray on Uint8ClampedList {
}
}
/// [JSInt16Array] <-> [Int16List]
// -----------------------------------------------------------------------------
// JSInt16Array <-> Int16List
@patch
extension JSInt16ArrayToInt16List on JSInt16Array {
@patch
@ -293,7 +315,8 @@ extension Int16ListToJSInt16Array on Int16List {
}
}
/// [JSUint16Array] <-> [Uint16List]
// -----------------------------------------------------------------------------
// JSUint16Array <-> Uint16List
@patch
extension JSUint16ArrayToInt16List on JSUint16Array {
@patch
@ -311,7 +334,8 @@ extension Uint16ListToJSInt16Array on Uint16List {
}
}
/// [JSInt32Array] <-> [Int32List]
// -----------------------------------------------------------------------------
// JSInt32Array <-> Int32List
@patch
extension JSInt32ArrayToInt32List on JSInt32Array {
@patch
@ -329,7 +353,8 @@ extension Int32ListToJSInt32Array on Int32List {
}
}
/// [JSUint32Array] <-> [Uint32List]
// -----------------------------------------------------------------------------
// JSUint32Array <-> Uint32List
@patch
extension JSUint32ArrayToUint32List on JSUint32Array {
@patch
@ -347,7 +372,8 @@ extension Uint32ListToJSUint32Array on Uint32List {
}
}
/// [JSFloat32Array] <-> [Float32List]
// -----------------------------------------------------------------------------
// JSFloat32Array <-> Float32List
@patch
extension JSFloat32ArrayToFloat32List on JSFloat32Array {
@patch
@ -366,7 +392,8 @@ extension Float32ListToJSFloat32Array on Float32List {
}
}
/// [JSFloat64Array] <-> [Float64List]
// -----------------------------------------------------------------------------
// JSFloat64Array <-> Float64List
@patch
extension JSFloat64ArrayToFloat64List on JSFloat64Array {
@patch
@ -385,7 +412,8 @@ extension Float64ListToJSFloat64Array on Float64List {
}
}
/// [JSArray] <-> [List]
// -----------------------------------------------------------------------------
// JSArray <-> List
@patch
extension JSArrayToList<T extends JSAny?> on JSArray<T> {
@patch
@ -410,7 +438,8 @@ extension ListToJSArray<T extends JSAny?> on List<T> {
_underlyingArray ?? _createJSProxyOfList<T>(this);
}
/// [JSNumber] -> [double] or [int].
// -----------------------------------------------------------------------------
// JSNumber -> double or int
@patch
extension JSNumberToNumber on JSNumber {
@patch
@ -434,7 +463,8 @@ extension DoubleToJSNumber on double {
JSNumber get toJS => JSNumber._(JSValue(toJSNumber(this)));
}
/// [JSBoolean] <-> [bool]
// -----------------------------------------------------------------------------
// JSBoolean <-> bool
@patch
extension JSBooleanToBool on JSBoolean {
@patch
@ -447,7 +477,8 @@ extension BoolToJSBoolean on bool {
JSBoolean get toJS => JSBoolean._(JSValue(toJSBoolean(this)));
}
/// [JSString] <-> [String]
// -----------------------------------------------------------------------------
// JSString <-> String
@patch
extension JSStringToString on JSString {
@patch
@ -575,12 +606,12 @@ class _Symbol {
@staticInterop
class __ListBackedJSArray {}
/// Implementation of indexing, `length`, and core handler methods.
///
/// JavaScript's `Array` methods are similar to Dart's `ListMixin`, because they
/// only rely on the implementation of `length` and indexing methods (and
/// support for any JS operators like `in` or `delete`).
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#generic_array_methods
// Implementation of indexing, `length`, and core handler methods.
//
// JavaScript's `Array` methods are similar to Dart's `ListMixin`, because they
// only rely on the implementation of `length` and indexing methods (and
// support for any JS operators like `in` or `delete`).
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#generic_array_methods
class _ListBackedJSArray {
final List<JSAny?> _list;
// The proxy that wraps this list.

View File

@ -83,7 +83,7 @@ typedef JSBigIntRepType = js.JSValue;
// 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;
typedef ExternalDartReferenceRepType<T> = 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.

View File

@ -56,9 +56,9 @@ external JSFunction? _jsWeakRefFunction;
final bool _supportsWeakRef = _jsWeakRefFunction != null;
@js_interop.JS('WeakRef')
extension type _JSWeakRef._(JSObject _) implements JSObject {
external _JSWeakRef(ExternalDartReference target);
external ExternalDartReference? deref();
extension type _JSWeakRef<T extends Object>._(JSObject _) implements JSObject {
external _JSWeakRef(ExternalDartReference<T> target);
external ExternalDartReference<T>? deref();
}
@patch
@ -77,10 +77,10 @@ class WeakReference<T extends Object> {
}
class _WeakReferenceWrapper<T extends Object> implements WeakReference<T> {
final _JSWeakRef _jsWeakRef;
final _JSWeakRef<T> _jsWeakRef;
_WeakReferenceWrapper(T target)
: _jsWeakRef = _JSWeakRef(target.toExternalReference);
T? get target => _jsWeakRef.deref()?.toDartObject as T?;
T? get target => _jsWeakRef.deref()?.toDartObject;
}
class _WeakReferencePolyfill<T extends Object> implements WeakReference<T> {
@ -93,14 +93,14 @@ external JSFunction? _jsFinalizationRegistry;
final bool _supportsFinalizationRegistry = _jsFinalizationRegistry != null;
@js_interop.JS('FinalizationRegistry')
extension type _JSFinalizationRegistry._(JSObject _) implements JSObject {
extension type _JSFinalizationRegistry<T>._(JSObject _) implements JSObject {
external _JSFinalizationRegistry(JSFunction callback);
@js_interop.JS('register')
external void registerWithDetach(ExternalDartReference value,
ExternalDartReference? peer, ExternalDartReference detach);
external void registerWithDetach(ExternalDartReference<Object> value,
ExternalDartReference<T> peer, ExternalDartReference<Object> detach);
external void register(
ExternalDartReference value, ExternalDartReference? peer);
external void unregister(ExternalDartReference detach);
ExternalDartReference<Object> value, ExternalDartReference<T> peer);
external void unregister(ExternalDartReference<Object> detach);
}
@patch
@ -134,8 +134,8 @@ class _FinalizationRegistryWrapper<T> implements Finalizer<T> {
_FinalizationRegistryWrapper(void Function(T) callback)
: _jsFinalizationRegistry =
_JSFinalizationRegistry(((ExternalDartReference? peer) {
callback(unsafeCast<T>(peer?.toDartObject));
_JSFinalizationRegistry(((ExternalDartReference<T> peer) {
callback(peer.toDartObject);
}).toJS);
void attach(Object value, T peer, {Object? detach}) {
@ -143,10 +143,10 @@ class _FinalizationRegistryWrapper<T> implements Finalizer<T> {
if (detach != null) {
_checkValidWeakTarget(detach);
_jsFinalizationRegistry.registerWithDetach(value.toExternalReference,
peer?.toExternalReference, detach.toExternalReference);
peer.toExternalReference, detach.toExternalReference);
} else {
_jsFinalizationRegistry.register(
value.toExternalReference, peer?.toExternalReference);
value.toExternalReference, peer.toExternalReference);
}
}

View File

@ -274,6 +274,8 @@ extension type JSBigInt._(JSBigIntRepType _jsBigInt) implements JSAny {}
/// 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.
/// The underlying representation type is nullable, meaning a non-nullable
/// [ExternalDartReference] may be `null`.
///
/// This interface is a faster alternative to [JSBoxedDartObject] by not
/// wrapping the Dart object with a JavaScript object. However, unlike
@ -290,9 +292,9 @@ extension type JSBigInt._(JSBigIntRepType _jsBigInt) implements JSAny {}
/// [ExternalDartReference].
///
/// See [ObjectToExternalDartReference.toExternalReference] to allow an
/// arbitrary [Object] to be passed to JavaScript.
extension type ExternalDartReference._(
ExternalDartReferenceRepType _externalDartReference) implements Object {}
/// arbitrary value of type [T] to be passed to JavaScript.
extension type ExternalDartReference<T extends Object?>._(
ExternalDartReferenceRepType<T> _externalDartReference) {}
/// JS type equivalent for `undefined` for interop member return types.
///
@ -380,8 +382,8 @@ extension JSAnyUtilityExtension on JSAny? {
/// Whether this <code>[JSAny]?</code> is an instance of the JavaScript type
/// that is declared by [T].
///
/// This method uses a combination of null, `typeof`, and `instanceof` checks
/// in order to do this check. Use this instead of `is` checks.
/// This method uses a combination of `null`, `typeof`, and `instanceof`
/// checks in order to do this check. Use this instead of `is` checks.
///
/// If [T] is a primitive JS type like [JSString], this uses a `typeof` check
/// that corresponds to that primitive type like `typeofEquals('string')`.
@ -405,7 +407,7 @@ extension JSAnyUtilityExtension on JSAny? {
/// `JSTypedArray`. As `TypedArray` does not exist as a property in
/// JavaScript, this does some prototype checking to make `isA<JSTypedArray>`
/// do the right thing. The other exception is `JSAny`. If you do a
/// `isA<JSAny>` check, it will only do a null-check.
/// `isA<JSAny>` check, it will only do a `null` check.
///
/// Using this method with a [T] that has an object literal constructor will
/// result in an error as you likely want to use [JSObject] instead.
@ -517,25 +519,27 @@ extension ObjectToJSBoxedDartObject on Object {
external JSBoxedDartObject get toJSBox;
}
/// Conversions from [ExternalDartReference] to [Object].
extension ExternalDartReferenceToObject on ExternalDartReference {
/// The Dart [Object] that this [ExternalDartReference] refers to.
/// Conversions from [ExternalDartReference] to the value of type [T].
extension ExternalDartReferenceToObject<T extends Object?>
on ExternalDartReference<T> {
/// The Dart value of type [T] 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;
external T get toDartObject;
}
/// Conversions from [Object] to [ExternalDartReference].
extension ObjectToExternalDartReference on Object {
/// An opaque reference to this [Object] which can be passed to JavaScript.
/// Conversions from a value of type [T] to [ExternalDartReference].
extension ObjectToExternalDartReference<T extends Object?> on T {
/// An opaque reference to this value of type [T] 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.
/// JavaScript value. If this value is `null`, returns `null`.
///
/// A value of type [ExternalDartReference] should be treated as completely
/// opaque. It can only be passed around as-is or converted back using
@ -546,7 +550,7 @@ extension ObjectToExternalDartReference on Object {
/// 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;
external ExternalDartReference<T> get toExternalReference;
}
/// Conversions from [JSPromise] to [Future].

View File

@ -151,16 +151,16 @@ extension on ENonInterop {
// [web] JS interop type or @Native type from an SDK web library required for 'external' extension members.
}
extension type EExternalDartReference._(ExternalDartReference _) {
extension type EExternalDartReference._(ExternalDartReference<Object> _) {
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 _) {}
extension type EExternalDartReference2._(ExternalDartReference<Object> _) {}
// ^
// [web] Extension type 'EExternalDartReference2' is marked with a '@JS' annotation, but its representation type is not a valid JS interop type: 'ExternalDartReference'.
// [web] Extension type 'EExternalDartReference2' is marked with a '@JS' annotation, but its representation type is not a valid JS interop type: 'ExternalDartReference<Object>'.
extension on EExternalDartReference {
external int field;

View File

@ -7,30 +7,34 @@
import 'dart:js_interop';
import 'package:expect/expect.dart';
import 'package:expect/variations.dart';
const isJSBackend = const bool.fromEnvironment('dart.library.html');
final bool isJSBackend = 1 is ExternalDartReference<int>;
extension type EExternalDartReference(ExternalDartReference _)
implements ExternalDartReference {}
extension type EExternalDartReference<T>(ExternalDartReference<T> _)
implements ExternalDartReference<T> {}
@JS()
external ExternalDartReference externalDartReference;
external ExternalDartReference<Object> externalDartReference;
@JS()
external EExternalDartReference eExternalDartReference;
external EExternalDartReference<DartClass> eExternalDartReference;
@JS('externalDartReference')
external ExternalDartReference? nullableExternalDartReference;
external ExternalDartReference<Object?> externalDartNullableReference;
@JS('externalDartReference')
external ExternalDartReference<Object>? nullableExternalDartReference;
// Use a function so that we can use a type parameter that extends an
// `ExternalDartReference` type.
@JS('identity')
external set _identity(JSFunction _);
@JS()
external T identity<T extends EExternalDartReference>(T t);
external T identity<T extends ExternalDartReference?>(T t);
extension type ObjectLiteral(JSObject _) {
external void operator []=(String key, ExternalDartReference value);
external void operator []=(String key, ExternalDartReference<Object> value);
}
class DartClass {
@ -39,23 +43,37 @@ class DartClass {
DartClass(this.field);
}
void main() {
final dartObject = DartClass(42);
class DartSubclass extends DartClass {
DartSubclass(super.field);
}
void generalTest() {
var dartObject = DartClass(42);
// `Object` test.
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));
_identity = ((ExternalDartReference e) => e).toJS;
Expect.identical(dartObject, externalDartReference.toDartObject as DartClass);
// Generic test.
var externalDartClassReference = dartObject.toExternalReference;
Expect.equals(dartObject, externalDartClassReference.toDartObject);
Expect.identical(dartObject, externalDartClassReference.toDartObject);
// Ensure we get assignability.
dartObject = externalDartClassReference.toDartObject;
// Check that we do the right thing with extension types on
// `ExternalDartReference`.
eExternalDartReference = EExternalDartReference(externalDartClassReference);
Expect.equals(dartObject, eExternalDartReference.toDartObject);
Expect.identical(dartObject, eExternalDartReference.toDartObject);
// Check that `ExternalDartReference` can be used as a parameter and return
// type for `Function.toJS`'d functions.
_identity = ((ExternalDartReference<DartClass> e) => e).toJS;
final externalDartReferenceTypeParam = identity(eExternalDartReference);
Expect.equals(
dartObject, externalDartReferenceTypeParam.toDartObject as DartClass);
Expect.isTrue(identical(
dartObject, externalDartReferenceTypeParam.toDartObject as DartClass));
Expect.equals(dartObject, externalDartReferenceTypeParam.toDartObject);
Expect.identical(dartObject, externalDartReferenceTypeParam.toDartObject);
// Multiple invocations should return the same underlying value, which is
// tested by `==`.
@ -63,27 +81,111 @@ void main() {
// 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));
Expect.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.
final jsString = ''.toJS;
Expect.equals(jsString.toExternalReference.toDartObject, jsString);
Expect.isTrue(identical(jsString.toExternalReference.toDartObject, jsString));
Expect.identical(jsString.toExternalReference.toDartObject, jsString);
// Check that we do the right thing with nullability still.
nullableExternalDartReference = null;
if (hasSoundNullSafety) Expect.throws(() => externalDartReference);
// Check that the type is checked when internalized for soundness.
externalDartReference = dartObject.toExternalReference;
Expect.throws(
() => (externalDartReference as ExternalDartReference<DartSubclass>)
// The cast is deferred until `toDartObject` for dart2wasm, so call it
// explicitly.
.toDartObject);
_identity = ((ExternalDartReference<DartSubclass> et) =>
et.toDartObject.toExternalReference).toJS;
Expect.throws(() => identity(externalDartReference));
// Check that we do the right thing with nullability still, both in the type
// parameter and outside it.
_identity = ((ExternalDartReference<Object> et) =>
et.toDartObject.toExternalReference).toJS;
nullableExternalDartReference = null?.toExternalReference;
Expect.isTrue(nullableExternalDartReference == null);
Expect.throwsWhen(
!unsoundNullSafety, () => externalDartReference.toDartObject);
Expect.throwsWhen(
!unsoundNullSafety, () => identity(nullableExternalDartReference));
externalDartNullableReference = null.toExternalReference;
Expect.isTrue(externalDartNullableReference == null);
Expect.throwsWhen(
!unsoundNullSafety, () => externalDartReference.toDartObject);
Expect.throwsWhen(
!unsoundNullSafety, () => identity(externalDartNullableReference));
// Check that they're both Dart `null`.
Expect.identical(
nullableExternalDartReference, externalDartNullableReference);
// Functions should not trigger `assertInterop`.
externalDartReference = () {}.toExternalReference;
identity(EExternalDartReference(() {}.toExternalReference));
final literal = ObjectLiteral(JSObject());
literal['ref'] = externalDartReference;
ObjectLiteral(JSObject())['ref'] = externalDartReference;
}
// An example interface for a generic `WritableSignal` from
// https://angular.dev/guide/signals that avoids unnecessary casts and wrapper
// functions in the JS compilers. The functions are stubbed to just test casts
// and assignability.
extension type WritableSignal<T>(JSFunction _) {
void _set(ExternalDartReference<T> value) {}
void set(T value) => _set(value.toExternalReference);
void _update(JSExportedDartFunction update) {}
void update(T Function(T) function) {
// Because `ExternalDartReference<T>`s are `T` on the JS backends, we can
// avoid the wrapper function that is needed for dart2wasm. If we want code
// that is guaranteed to work on all backends, the wrapper function will
// work, but will be slower on the JS backends. See
// https://github.com/dart-lang/sdk/issues/55342 for more details.
if (isJSBackend) {
_update((function as ExternalDartReference<T> Function(
ExternalDartReference<T>))
.toJS);
}
// Should work on all backends.
_update(((ExternalDartReference<T> e) =>
function(e.toDartObject).toExternalReference).toJS);
}
}
WritableSignal<T> signal<T>(T initialValue) =>
WritableSignal<T>((() => initialValue.toExternalReference).toJS);
void signalsTest() {
final writableSignal = signal<Object?>(0);
writableSignal.set(null);
writableSignal.set(true);
writableSignal.set(DartClass(42));
writableSignal.update((Object? x) => x);
writableSignal.update((_) => false);
writableSignal.update((Object? _) => null);
final writableIntSignal = signal(null as int?);
writableIntSignal.set(null);
writableIntSignal.set(0);
writableIntSignal.update((int? x) => x);
writableIntSignal.update((int? _) => null);
writableIntSignal.update((num? _) => 0);
final writableDartSignal = signal<DartClass?>(DartClass(42));
writableDartSignal.set(null);
writableDartSignal.set(DartSubclass(42));
writableDartSignal.update((DartClass? x) => x);
writableDartSignal.update((DartClass? x) => x as DartSubclass);
writableDartSignal.update((Object? _) => DartClass(42));
}
void main() {
generalTest();
signalsTest();
}