[dart:js_interop] Reach (mostly) parity with js_util

https://github.com/dart-lang/sdk/issues/54004

In order to reach parity with js_util, we expose operators
through an extension and do some partial renames in order
to make the member names sound better e.g. `equals` instead
of `equal`. We also expose the following from js_util:

- NullRejectionException

We don't expose `isJavaScriptArray` and `isJavaScriptSimpleObject`
as they can expressed through other forms of interop. There
was an esoteric bug where we needed these members for Firefox
in pkg:test, but the package no longer uses these members, so to
avoid increasing the API space too much, we do not export them.

For the same reason, we also don't expose `objectGetPrototypeOf`,
`objectPrototype`, `objectKeys`.

We don't expose `allowInteropCaptureThis` as it will take some
work to handle this in dart2wasm. That work is tracked in
https://github.com/dart-lang/sdk/issues/54381.

Lastly, `instanceof` and `instanceOfString` is moved to be on
`JSAny?`, as this operator is useful to avoid needing to
downcast to `JSObject` e.g. `any.instanceOfString('Window')`
instead of `any.typeofEquals('object') &&
(any as JSObject).instanceOfString('Window')`.

Extensions are reorganized and renamed to handle these changes.

CoreLibraryReviewExempt: Backend-specific library.
Change-Id: Ib1a7fabc3fa985ef6638620becccd27eeca68c25
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/341140
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Srujan Gaddam 2023-12-25 20:02:49 +00:00 committed by Commit Queue
parent 5410d97493
commit 6fbd07f2b0
15 changed files with 589 additions and 239 deletions

View file

@ -81,6 +81,11 @@
types whose type parameter is a subtype of `JSAny?`. Conversions to and from
these types are changed to account for the type parameters of the Dart or JS
type, respectively.
- **Breaking Change in names of extensions**: Some `dart:js_interop` extension
members are moved to different extensions on the same type or a supertype to
better organize the API surface. See `JSAnyUtilityExtension` and
`JSAnyOperatorExtension` for the new extensions. This shouldn't make a
difference unless the extension names were explicitly used.
[#52687]: https://github.com/dart-lang/sdk/issues/52687

View file

@ -9016,8 +9016,9 @@ const Code<Null> codeJsInteropOperatorsNotSupported =
const MessageCode messageJsInteropOperatorsNotSupported = const MessageCode(
"JsInteropOperatorsNotSupported",
problemMessage:
r"""JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.""",
correctionMessage: r"""Try replacing this with a normal method.""");
r"""JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.""",
correctionMessage:
r"""Try making this class a static interop type instead.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeJsInteropStaticInteropGenerativeConstructor =

View file

@ -303,8 +303,6 @@ class JsInteropChecks extends RecursiveVisitor {
void report(Message message) => _reporter.report(
message, node.fileOffset, node.name.text.length, node.fileUri);
// TODO(joshualitt): Add a check that only supported operators are allowed
// in external extension members and extension types.
_checkInstanceMemberJSAnnotation(node);
if (_classHasJSAnnotation &&
!node.isExternal &&
@ -319,7 +317,7 @@ class JsInteropChecks extends RecursiveVisitor {
if (!_isJSInteropMember(node)) {
_checkDisallowedExternal(node);
} else {
_checkJsInteropMemberNotOperator(node);
_checkJsInteropOperator(node);
// Check JS Interop positional and named parameters. Literal constructors
// can only have named parameters, and every other interop member can only
@ -735,10 +733,8 @@ class JsInteropChecks extends RecursiveVisitor {
}
/// Given JS interop member [node], checks that it is not an operator that is
/// disallowed.
///
/// Also checks that no renaming is done on interop operators.
void _checkJsInteropMemberNotOperator(Procedure node) {
/// disallowed, on a non-static interop type, or renamed.
void _checkJsInteropOperator(Procedure node) {
var isInvalidOperator = false;
var operatorHasRenaming = false;
if ((node.isExtensionTypeMember &&

View file

@ -274,25 +274,19 @@ class JsUtilOptimizer extends Transformer {
[Expression? maybeReceiver]) {
final target =
shouldTrustType ? _getPropertyTrustTypeTarget : _getPropertyTarget;
final isOperator = _extensionIndex.isOperator(node);
final isInstanceInteropMember =
_extensionIndex.isInstanceInteropMember(node);
final name = _getMemberJSName(node);
return (Arguments arguments, Expression invocation) {
// Parameter `this` only exists for extension and extension type instance
// members. Operators take a `this` and an index.
// members.
final positionalArgs = arguments.positional;
assert(positionalArgs.length ==
(isOperator
? 2
: isInstanceInteropMember
? 1
: 0));
assert(positionalArgs.length == (isInstanceInteropMember ? 1 : 0));
// We clone the receiver as each invocation needs a fresh node.
final receiver = maybeReceiver == null
? positionalArgs.first
: _cloner.clone(maybeReceiver);
final property = isOperator ? positionalArgs[1] : StringLiteral(name);
final property = StringLiteral(name);
return StaticInvocation(
target,
Arguments([receiver, property],
@ -311,24 +305,18 @@ class JsUtilOptimizer extends Transformer {
/// positional argument as the receiver for `js_util.setProperty`.
_InvocationBuilder _getExternalSetterInvocationBuilder(Procedure node,
[Expression? maybeReceiver]) {
final isOperator = _extensionIndex.isOperator(node);
final isInstanceInteropMember =
_extensionIndex.isInstanceInteropMember(node);
final name = _getMemberJSName(node);
return (Arguments arguments, Expression invocation) {
// Parameter `this` only exists for extension and extension type instance
// members. Operators take a `this`, an index, and a value.
// members.
final positionalArgs = arguments.positional;
assert(positionalArgs.length ==
(isOperator
? 3
: isInstanceInteropMember
? 2
: 1));
assert(positionalArgs.length == (isInstanceInteropMember ? 2 : 1));
final receiver = maybeReceiver == null
? positionalArgs.first
: _cloner.clone(maybeReceiver);
final property = isOperator ? positionalArgs[1] : StringLiteral(name);
final property = StringLiteral(name);
final value = positionalArgs.last;
return StaticInvocation(
_setPropertyTarget,
@ -389,14 +377,32 @@ class JsUtilOptimizer extends Transformer {
final operator =
_extensionIndex.getExtensionTypeDescriptor(node)?.name.text ??
_extensionIndex.getExtensionDescriptor(node)?.name.text;
// TODO(srujzs): Support more operators for overloading using some
// combination of Dart-defineable operators and @JS renaming for the ones
// that are not renameable.
late Procedure target;
switch (operator) {
case '[]':
return _getExternalGetterInvocationBuilder(node, shouldTrustType);
target =
shouldTrustType ? _getPropertyTrustTypeTarget : _getPropertyTarget;
break;
case '[]=':
return _getExternalSetterInvocationBuilder(node);
target = _setPropertyTarget;
break;
default:
throw 'External operator $operator is unsupported for static interop.';
throw UnimplementedError(
'External operator $operator is unsupported for static interop. ');
}
return (Arguments arguments, Expression invocation) {
return StaticInvocation(
target,
Arguments(arguments.positional,
types: [invocation.getStaticType(_staticTypeContext)]))
..fileOffset = invocation.fileOffset
..parent = invocation.parent;
};
}
/// Returns a new [_InvocationBuilder] for the given [node] external

View file

@ -200,12 +200,16 @@ class _OperatorSpecializer extends _ProcedureSpecializer {
@override
String bodyString(String object, List<String> callArguments) {
if (jsString == '[]') {
return '$object[${callArguments[0]}]';
} else if (jsString == '[]=') {
return '$object[${callArguments[0]}] = ${callArguments[1]}';
} else {
throw 'Unsupported operator: $jsString';
// TODO(srujzs): Switch to switch-case expression once we update pubspec.
switch (jsString) {
case '[]':
return '$object[${callArguments[0]}]';
case '[]=':
return '$object[${callArguments[0]}] = ${callArguments[1]}';
default:
throw UnimplementedError(
'External operator $jsString is unsupported for static interop. '
'Please file a request in the SDK if you want it to be supported.');
}
}
}

View file

@ -5904,8 +5904,8 @@ JsInteropOperatorCannotBeRenamed:
correctionMessage: "Remove the annotation or remove the value inside the annotation."
JsInteropOperatorsNotSupported:
problemMessage: "JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop."
correctionMessage: "Try replacing this with a normal method."
problemMessage: "JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop."
correctionMessage: "Try making this class a static interop type instead."
JsInteropNonStaticWithStaticInteropSupertype:
problemMessage: "Class '#name' does not have an `@staticInterop` annotation, but has supertype '#name2', which does."

View file

@ -29,12 +29,20 @@ extension NullableUndefineableJSAnyExtension on JSAny? {
@patch
@pragma('dart2js:prefer-inline')
bool get isNull => foreign_helper.JS('bool', '# === null', this);
}
@patch
extension JSAnyUtilityExtension on JSAny? {
@patch
@pragma('dart2js:prefer-inline')
bool typeofEquals(String typeString) =>
foreign_helper.JS('bool', 'typeof # === #', this, typeString);
@patch
@pragma('dart2js:prefer-inline')
bool instanceof(JSFunction constructor) =>
foreign_helper.JS('bool', '# instanceof #', this, constructor);
@patch
@pragma('dart2js:prefer-inline')
Object? dartify() => js_util.dartify(this);
@ -48,15 +56,6 @@ extension NullableObjectUtilExtension on Object? {
JSAny? jsify() => js_util.jsify(this) as JSAny?;
}
/// Utility extensions for [JSObject].
@patch
extension JSObjectUtilExtension on JSObject {
@patch
@pragma('dart2js:prefer-inline')
bool instanceof(JSFunction constructor) =>
foreign_helper.JS('bool', '# instanceof #', this, constructor);
}
/// [JSExportedDartFunction] <-> [Function]
@patch
extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
@ -368,3 +367,84 @@ extension StringToJSString on String {
@pragma('dart2js:prefer-inline')
JSString get toJS => this as JSString;
}
@patch
extension JSAnyOperatorExtension on JSAny? {
@patch
@pragma('dart2js:prefer-inline')
JSAny add(JSAny? any) => js_util.add(this, any);
@patch
@pragma('dart2js:prefer-inline')
JSAny subtract(JSAny? any) => js_util.subtract(this, any);
@patch
@pragma('dart2js:prefer-inline')
JSAny multiply(JSAny? any) => js_util.multiply(this, any);
@patch
@pragma('dart2js:prefer-inline')
JSAny divide(JSAny? any) => js_util.divide(this, any);
@patch
@pragma('dart2js:prefer-inline')
JSAny modulo(JSAny? any) => js_util.modulo(this, any);
@patch
@pragma('dart2js:prefer-inline')
JSAny exponentiate(JSAny? any) => js_util.exponentiate(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool greaterThan(JSAny? any) => js_util.greaterThan(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool greaterThanOrEqualTo(JSAny? any) =>
js_util.greaterThanOrEqual(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool lessThan(JSAny? any) => js_util.lessThan(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool lessThanOrEqualTo(JSAny? any) => js_util.lessThanOrEqual(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool equals(JSAny? any) => js_util.equal(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool notEquals(JSAny? any) => js_util.notEqual(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool strictEquals(JSAny? any) => js_util.strictEqual(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool strictNotEquals(JSAny? any) => js_util.strictNotEqual(this, any);
@patch
@pragma('dart2js:prefer-inline')
JSNumber unsignedRightShift(JSAny? any) =>
js_util.unsignedRightShift(this, any).toJS;
@patch
@pragma('dart2js:prefer-inline')
JSAny? and(JSAny? any) => js_util.and(this, any);
@patch
@pragma('dart2js:prefer-inline')
JSAny? or(JSAny? any) => js_util.or(this, any);
@patch
@pragma('dart2js:prefer-inline')
bool get not => js_util.not(this);
@patch
@pragma('dart2js:prefer-inline')
bool get isTruthy => js_util.isTruthy(this);
}

View file

@ -14,13 +14,15 @@ import 'dart:js_util' as js_util;
import 'dart:typed_data';
/// Some helpers for working with JS types internally. If we implement the JS
/// types as inline classes then these should go away.
/// types as inline classes then these should go away. We avoid doing a
/// null-check check if we know the value is guaranteed to be non-nullable.
/// TODO(joshualitt): Find a way to get rid of the explicit casts.
T _box<T>(WasmExternRef? ref) => JSValue(ref) as T;
T _boxNonNullable<T>(WasmExternRef? ref) => JSValue(ref) as T;
T _boxNullable<T>(WasmExternRef? ref) => JSValue.box(ref) as T;
@patch
js_types.JSObjectRepType _createObjectLiteral() =>
_box<js_types.JSObjectRepType>(js_helper.newObjectRaw());
_boxNonNullable<js_types.JSObjectRepType>(js_helper.newObjectRaw());
// This should match the global context we use in our static interop lowerings.
@patch
@ -43,13 +45,22 @@ extension NullableUndefineableJSAnyExtension on JSAny? {
throw UnimplementedError("JS 'null' and 'undefined' are internalized as "
"Dart null in dart2wasm. As such, they can not be differentiated and "
"this API should not be used when compiling to Wasm.");
}
@patch
extension JSAnyUtilityExtension on JSAny? {
@patch
bool typeofEquals(String type) => js_helper
.JS<WasmI32>(
'(o, t) => typeof o === t', this.toExternRef, type.toJS.toExternRef)
.toBool();
@patch
bool instanceof(JSFunction constructor) => js_helper
.JS<WasmI32>(
'(o, c) => o instanceof c', toExternRef, constructor.toExternRef)
.toBool();
@patch
Object? dartify() => js_util.dartify(this);
}
@ -61,16 +72,6 @@ extension NullableObjectUtilExtension on Object? {
JSAny? jsify() => js_util.jsify(this) as JSAny?;
}
/// Utility extensions for [JSObject].
@patch
extension JSObjectUtilExtension on JSObject {
@patch
bool instanceof(JSFunction constructor) => js_helper
.JS<WasmI32>(
'(o, c) => o instanceof c', toExternRef, constructor.toExternRef)
.toBool();
}
/// [JSExportedDartFunction] <-> [Function]
@patch
extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
@ -107,7 +108,7 @@ extension ObjectToJSBoxedDartObject on Object {
if (this is JSValue) {
throw 'Attempting to box non-Dart object.';
}
return _box<JSBoxedDartObject>(jsObjectFromDartObject(this));
return _boxNonNullable<JSBoxedDartObject>(jsObjectFromDartObject(this));
}
}
@ -162,7 +163,7 @@ extension ByteBufferToJSArrayBuffer on ByteBuffer {
@patch
JSArrayBuffer get toJS {
final t = this;
return _box<JSArrayBuffer>(t is js_types.JSArrayBufferImpl
return _boxNonNullable<JSArrayBuffer>(t is js_types.JSArrayBufferImpl
? t.toExternRef
: jsArrayBufferFromDartByteBuffer(t));
}
@ -180,7 +181,7 @@ extension ByteDataToJSDataView on ByteData {
@patch
JSDataView get toJS {
final t = this;
return _box<JSDataView>(t is js_types.JSDataViewImpl
return _boxNonNullable<JSDataView>(t is js_types.JSDataViewImpl
? t.toExternRef
: jsDataViewFromDartByteData(t, lengthInBytes.toDouble()));
}
@ -198,7 +199,7 @@ extension Int8ListToJSInt8Array on Int8List {
@patch
JSInt8Array get toJS {
final t = this;
return _box<JSInt8Array>(t is js_types.JSInt8ArrayImpl
return _boxNonNullable<JSInt8Array>(t is js_types.JSInt8ArrayImpl
? t.toJSArrayExternRef()
: jsInt8ArrayFromDartInt8List(t));
}
@ -216,7 +217,7 @@ extension Uint8ListToJSUint8Array on Uint8List {
@patch
JSUint8Array get toJS {
final t = this;
return _box<JSUint8Array>(t is js_types.JSUint8ArrayImpl
return _boxNonNullable<JSUint8Array>(t is js_types.JSUint8ArrayImpl
? t.toJSArrayExternRef()
: jsUint8ArrayFromDartUint8List(t));
}
@ -235,9 +236,10 @@ extension Uint8ClampedListToJSUint8ClampedArray on Uint8ClampedList {
@patch
JSUint8ClampedArray get toJS {
final t = this;
return _box<JSUint8ClampedArray>(t is js_types.JSUint8ClampedArrayImpl
? t.toJSArrayExternRef()
: jsUint8ClampedArrayFromDartUint8ClampedList(t));
return _boxNonNullable<JSUint8ClampedArray>(
t is js_types.JSUint8ClampedArrayImpl
? t.toJSArrayExternRef()
: jsUint8ClampedArrayFromDartUint8ClampedList(t));
}
}
@ -253,7 +255,7 @@ extension Int16ListToJSInt16Array on Int16List {
@patch
JSInt16Array get toJS {
final t = this;
return _box<JSInt16Array>(t is js_types.JSInt16ArrayImpl
return _boxNonNullable<JSInt16Array>(t is js_types.JSInt16ArrayImpl
? t.toJSArrayExternRef()
: jsInt16ArrayFromDartInt16List(t));
}
@ -271,7 +273,7 @@ extension Uint16ListToJSInt16Array on Uint16List {
@patch
JSUint16Array get toJS {
final t = this;
return _box<JSUint16Array>(t is js_types.JSUint16ArrayImpl
return _boxNonNullable<JSUint16Array>(t is js_types.JSUint16ArrayImpl
? t.toJSArrayExternRef()
: jsUint16ArrayFromDartUint16List(t));
}
@ -289,7 +291,7 @@ extension Int32ListToJSInt32Array on Int32List {
@patch
JSInt32Array get toJS {
final t = this;
return _box<JSInt32Array>(t is js_types.JSInt32ArrayImpl
return _boxNonNullable<JSInt32Array>(t is js_types.JSInt32ArrayImpl
? t.toJSArrayExternRef()
: jsInt32ArrayFromDartInt32List(t));
}
@ -307,7 +309,7 @@ extension Uint32ListToJSUint32Array on Uint32List {
@patch
JSUint32Array get toJS {
final t = this;
return _box<JSUint32Array>(t is js_types.JSUint32ArrayImpl
return _boxNonNullable<JSUint32Array>(t is js_types.JSUint32ArrayImpl
? t.toJSArrayExternRef()
: jsUint32ArrayFromDartUint32List(t));
}
@ -326,7 +328,7 @@ extension Float32ListToJSFloat32Array on Float32List {
@patch
JSFloat32Array get toJS {
final t = this;
return _box<JSFloat32Array>(t is js_types.JSFloat32ArrayImpl
return _boxNonNullable<JSFloat32Array>(t is js_types.JSFloat32ArrayImpl
? t.toJSArrayExternRef()
: jsFloat32ArrayFromDartFloat32List(t));
}
@ -345,7 +347,7 @@ extension Float64ListToJSFloat64Array on Float64List {
@patch
JSFloat64Array get toJS {
final t = this;
return _box<JSFloat64Array>(t is js_types.JSFloat64ArrayImpl
return _boxNonNullable<JSFloat64Array>(t is js_types.JSFloat64ArrayImpl
? t.toJSArrayExternRef()
: jsFloat64ArrayFromDartFloat64List(t));
}
@ -397,7 +399,7 @@ extension JSNumberToNumber on JSNumber {
@patch
extension DoubleToJSNumber on double {
@patch
JSNumber get toJS => _box<JSNumber>(toJSNumber(this));
JSNumber get toJS => _boxNonNullable<JSNumber>(toJSNumber(this));
}
/// [JSBoolean] <-> [bool]
@ -410,7 +412,7 @@ extension JSBooleanToBool on JSBoolean {
@patch
extension BoolToJSBoolean on bool {
@patch
JSBoolean get toJS => _box<JSBoolean>(toJSBoolean(this));
JSBoolean get toJS => _boxNonNullable<JSBoolean>(toJSBoolean(this));
}
/// [JSString] <-> [String]
@ -425,11 +427,114 @@ extension StringToJSString on String {
@patch
JSString get toJS {
final t = this;
return _box<JSString>(
return _boxNonNullable<JSString>(
t is js_types.JSStringImpl ? t.toExternRef : jsStringFromDartString(t));
}
}
@patch
extension JSAnyOperatorExtension on JSAny? {
@patch
JSAny add(JSAny? any) => _boxNonNullable<JSAny>(js_helper.JS<WasmExternRef?>(
'(o, a) => o + a', this.toExternRef, any.toExternRef));
@patch
JSAny subtract(JSAny? any) =>
_boxNonNullable<JSAny>(js_helper.JS<WasmExternRef?>(
'(o, a) => o - a', this.toExternRef, any.toExternRef));
@patch
JSAny multiply(JSAny? any) =>
_boxNonNullable<JSAny>(js_helper.JS<WasmExternRef?>(
'(o, a) => o * a', this.toExternRef, any.toExternRef));
@patch
JSAny divide(JSAny? any) =>
_boxNonNullable<JSAny>(js_helper.JS<WasmExternRef?>(
'(o, a) => o / a', this.toExternRef, any.toExternRef));
@patch
JSAny modulo(JSAny? any) =>
_boxNonNullable<JSAny>(js_helper.JS<WasmExternRef?>(
'(o, a) => o % a', this.toExternRef, any.toExternRef));
@patch
JSAny exponentiate(JSAny? any) =>
_boxNonNullable<JSAny>(js_helper.JS<WasmExternRef?>(
'(o, a) => o ** a', this.toExternRef, any.toExternRef));
@patch
bool greaterThan(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o > a', this.toExternRef, any.toExternRef))
.toDart;
@patch
bool greaterThanOrEqualTo(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o >= a', this.toExternRef, any.toExternRef))
.toDart;
@patch
bool lessThan(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o < a', this.toExternRef, any.toExternRef))
.toDart;
@patch
bool lessThanOrEqualTo(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o <= a', this.toExternRef, any.toExternRef))
.toDart;
@patch
bool equals(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o == a', this.toExternRef, any.toExternRef))
.toDart;
@patch
bool notEquals(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o != a', this.toExternRef, any.toExternRef))
.toDart;
@patch
bool strictEquals(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o === a', this.toExternRef, any.toExternRef))
.toDart;
@patch
bool strictNotEquals(JSAny? any) =>
_boxNonNullable<JSBoolean>(js_helper.JS<WasmExternRef?>(
'(o, a) => o !== a', this.toExternRef, any.toExternRef))
.toDart;
@patch
JSNumber unsignedRightShift(JSAny? any) =>
_boxNonNullable<JSNumber>(js_helper.JS<WasmExternRef?>(
'(o, a) => o >>> a', this.toExternRef, any.toExternRef));
@patch
JSAny? and(JSAny? any) => _boxNullable<JSAny?>(js_helper.JS<WasmExternRef?>(
'(o, a) => o && a', this.toExternRef, any.toExternRef));
@patch
JSAny? or(JSAny? any) => _boxNullable<JSAny?>(js_helper.JS<WasmExternRef?>(
'(o, a) => o || a', this.toExternRef, any.toExternRef));
@patch
bool get not => _boxNonNullable<JSBoolean>(
js_helper.JS<WasmExternRef?>('(o) => !o', this.toExternRef))
.toDart;
@patch
bool get isTruthy => _boxNonNullable<JSBoolean>(
js_helper.JS<WasmExternRef?>('(o) => !!o', this.toExternRef))
.toDart;
}
@JS('Array')
@staticInterop
class _Array {
@ -511,7 +616,7 @@ JSArray<T> _createJSProxyOfList<T extends JSAny?>(List<T> list) {
final hasIndex = jsExportWrapper['_hasIndex']!.toExternRef;
final deleteIndex = jsExportWrapper['_deleteIndex']!.toExternRef;
final proxy = _box<JSArray<T>>(js_helper.JS<WasmExternRef?>('''
final proxy = _boxNonNullable<JSArray<T>>(js_helper.JS<WasmExternRef?>('''
(wrapper, getIndex, setIndex, hasIndex, deleteIndex) => new Proxy(wrapper, {
'get': function (target, prop, receiver) {
if (typeof prop == 'string') {

View file

@ -26,6 +26,7 @@ import 'dart:typed_data';
/// Allow use of `@staticInterop` classes with JS types as well as export
/// functionality.
export 'dart:_js_annotations' show staticInterop, anonymous, JSExport;
export 'dart:js_util' show NullRejectionException;
/// The annotation for JS interop members.
///
@ -247,8 +248,23 @@ extension NullableUndefineableJSAnyExtension on JSAny? {
bool get isUndefinedOrNull => this == null;
bool get isDefinedAndNotNull => !isUndefinedOrNull;
}
/// Common utility functions that are useful for any JS value.
extension JSAnyUtilityExtension on JSAny? {
/// Returns whether the result of `typeof` on this [JSAny]? is [typeString].
external bool typeofEquals(String typeString);
/// Returns whether this [JSAny]? is an `instanceof` [constructor].
external bool instanceof(JSFunction constructor);
/// Like [instanceof], but only takes a [String] for the constructor name,
/// which is then looked up in the [globalContext].
bool instanceOfString(String constructorName) {
final constructor = globalContext[constructorName] as JSFunction?;
return constructor != null && instanceof(constructor);
}
/// Effectively the inverse of [jsify], [dartify] Takes a JavaScript object,
/// and converts it to a Dart based object. Only JS primitives, arrays, or
/// 'map' like JS objects are supported.
@ -262,18 +278,6 @@ extension NullableObjectUtilExtension on Object? {
external JSAny? jsify();
}
/// Utility extensions for [JSObject].
extension JSObjectUtilExtension on JSObject {
external bool instanceof(JSFunction constructor);
/// Like [instanceof], but only takes a [String] for the constructor name,
/// which is then looked up in the [globalContext].
bool instanceOfString(String constructorName) {
final constructor = globalContext[constructorName] as JSFunction?;
return constructor != null && instanceof(constructor);
}
}
/// The type of `JSUndefined` when returned from functions. Unlike pure JS,
/// no actual object will be returned.
// TODO(srujzs): Should we just remove this? There are no performance costs from
@ -544,3 +548,76 @@ extension JSStringToString on JSString {
extension StringToJSString on String {
external JSString get toJS;
}
// General-purpose operators.
//
// Indexing operators (`[]`, `[]=`) should be declared through operator
// overloading instead e.g. `external operator int [](int key);`.
// TODO(srujzs): Add more as needed. For now, we just expose the ones needed to
// migrate from `dart:js_util`.
extension JSAnyOperatorExtension on JSAny? {
// Artithmetic operators.
/// Returns the result of '[this] + [any]' in JS.
external JSAny add(JSAny? any);
/// Returns the result of '[this] - [any]' in JS.
external JSAny subtract(JSAny? any);
/// Returns the result of '[this] * [any]' in JS.
external JSAny multiply(JSAny? any);
/// Returns the result of '[this] / [any]' in JS.
external JSAny divide(JSAny? any);
/// Returns the result of '[this] % [any]' in JS.
external JSAny modulo(JSAny? any);
/// Returns the result of '[this] ** [any]' in JS.
external JSAny exponentiate(JSAny? any);
// Comparison operators.
/// Returns the result of '[this] > [any]' in JS.
external JSBoolean greaterThan(JSAny? any);
/// Returns the result of '[this] >= [any]' in JS.
external JSBoolean greaterThanOrEqualTo(JSAny? any);
/// Returns the result of '[this] < [any]' in JS.
external JSBoolean lessThan(JSAny? any);
/// Returns the result of '[this] <= [any]' in JS.
external JSBoolean lessThanOrEqualTo(JSAny? any);
/// Returns the result of '[this] == [any]' in JS.
external JSBoolean equals(JSAny? any);
/// Returns the result of '[this] != [any]' in JS.
external JSBoolean notEquals(JSAny? any);
/// Returns the result of '[this] === [any]' in JS.
external JSBoolean strictEquals(JSAny? any);
/// Returns the result of '[this] !== [any]' in JS.
external JSBoolean strictNotEquals(JSAny? any);
// Bitwise operators.
/// Returns the result of '[this] >>> [any]' in JS.
external JSNumber unsignedRightShift(JSAny? any);
// Logical operators.
/// Returns the result of '[this] && [any]' in JS.
external JSAny? and(JSAny? any);
/// Returns the result of '[this] || [any]' in JS.
external JSAny? or(JSAny? any);
/// Returns the result of '![this]' in JS.
external bool get not;
/// Returns the result of '!![this]' in JS.
external bool get isTruthy;
}

View file

@ -11,131 +11,135 @@ import 'package:js/js.dart';
class JSClass {
// https://dart.dev/guides/language/language-tour#_operators for the list of
// operators allowed by the language.
@JS('rename')
external void operator <(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop operator methods cannot be renamed using the '@JS' annotation.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <=(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >=(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator -(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator +(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator /(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ~/(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator *(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator %(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator |(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ^(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator &(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <<(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>>(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator [](_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator []=(_, __);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ~();
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external bool operator ==(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
}
@JS()
@anonymous
class AnonymousClass {
@JS('rename')
external void operator <(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop operator methods cannot be renamed using the '@JS' annotation.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <=(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >=(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator -(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator +(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator /(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ~/(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator *(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator %(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator |(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ^(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator &(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <<(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>>(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator [](_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator []=(_, __);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ~();
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external bool operator ==(_);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
}
void main() {}

View file

@ -1,72 +0,0 @@
// 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.
// SharedOptions=--enable-experiment=inline-class
@JS()
library external_operator_test;
import 'dart:js_interop';
import 'package:expect/minitest.dart';
@JS()
external void eval(String code);
@JS()
@staticInterop
class Indexable {}
extension IndexableExtension on Indexable {
external JSAny? operator [](JSNumber index);
external void operator []=(JSNumber index, JSAny? value);
}
@JS()
external Indexable get indexableArr;
@JS()
external Indexable get indexableObj;
@JS()
extension type Indexable2(JSObject _) {
external JSAny? operator [](JSNumber index);
external void operator []=(JSNumber index, JSAny? value);
}
@JS('indexableArr')
external Indexable2 get indexableArr2;
@JS('indexableObj')
external Indexable2 get indexableObj2;
void main() {
eval('''
globalThis.indexableArr = [];
globalThis.indexableObj = {};
''');
// [JSObject] should be indexable.
{
final obj = indexableObj;
obj[3.0.toJS] = 4.0.toJS;
expect((obj[3.0.toJS] as JSNumber).toDartDouble, 4.0);
}
{
final obj = indexableObj2;
obj[4.0.toJS] = 5.0.toJS;
expect((obj[4.0.toJS] as JSNumber).toDartDouble, 5.0);
}
// [JSArray] should be indexable.
{
final arr = indexableArr;
arr[5.0.toJS] = 6.0.toJS;
expect((arr[5.0.toJS] as JSNumber).toDartDouble, 6.0);
}
{
final arr = indexableArr2;
arr[6.0.toJS] = 7.0.toJS;
expect((arr[6.0.toJS] as JSNumber).toDartDouble, 7.0);
}
}

View file

@ -6,7 +6,6 @@
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:js_util';
import 'dart:typed_data';
import 'package:expect/expect.dart';

View file

@ -5,7 +5,7 @@
// SharedOptions=--enable-experiment=inline-class
@JS()
library operator_test;
library operator_static_test;
import 'dart:js_interop';
@ -13,57 +13,57 @@ import 'dart:js_interop';
@staticInterop
class StaticInterop {}
extension _ on StaticInterop {
extension on StaticInterop {
// https://dart.dev/guides/language/language-tour#_operators for the list of
// operators allowed by the language.
external void operator <(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <=(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >=(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator -(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator +(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator /(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ~/(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator *(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator %(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator |(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ^(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator &(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <<(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>>(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
@JS('rename')
external void operator [](JSAny _);
// ^
@ -74,7 +74,7 @@ extension _ on StaticInterop {
// [web] JS interop operator methods cannot be renamed using the '@JS' annotation.
external void operator ~();
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
// No `==` as it's an `Object` method.
}
@ -83,52 +83,52 @@ extension _ on StaticInterop {
extension type ExtensionType(JSObject _) {
external void operator <(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <=(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >=(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator -(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator +(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator /(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ~/(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator *(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator %(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator |(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator ^(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator &(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator <<(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
external void operator >>>(JSAny _);
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
@JS('rename')
external void operator [](JSAny _);
// ^
@ -139,7 +139,7 @@ extension type ExtensionType(JSObject _) {
// [web] JS interop operator methods cannot be renamed using the '@JS' annotation.
external void operator ~();
// ^
// [web] JS interop classes do not support operator methods, with the exception of '[]' and '[]=' using static interop.
// [web] JS interop types do not support overloading external operator methods, with the exception of '[]' and '[]=' using static interop.
// No `==` as it's an `Object` method.
}

View file

@ -0,0 +1,143 @@
// 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.
// SharedOptions=--enable-experiment=inline-class
// Test operators through overloading and the ones defined in dart:js_interop.
@JS()
library operator_test;
import 'dart:js_interop';
import 'package:expect/expect.dart';
import 'package:expect/minitest.dart';
@JS()
extension type OperableExtType(JSObject _) {
external int operator [](int index);
external void operator []=(int index, int value);
}
void extensionTypeTest() {
final obj = OperableExtType(JSObject());
obj[4] = 5;
expect(obj[4], 5);
final arr = OperableExtType(JSArray());
arr[6] = 7;
expect(arr[6], 7);
}
@JS()
@staticInterop
class Operable {}
extension OperableExtension on Operable {
external int operator [](int index);
external void operator []=(int index, int value);
}
void staticInteropTest() {
final obj = JSObject() as Operable;
obj[4] = 5;
expect(obj[4], 5);
final arr = JSArray() as Operable;
arr[5] = 6;
expect(arr[5], 6);
}
@JS()
external bool isNaN(JSAny? number);
@JS()
external JSBigInt BigInt(String value);
extension on int {
JSBigInt get toBigInt => BigInt(this.toString());
}
void dartJsInteropOperatorsTest() {
// Arithmetic.
final i10 = 10.toJS;
expect(i10.add(1.toJS), 11.toJS);
expect(i10.subtract(1.toJS), 9.toJS);
expect(i10.multiply(2.toJS), 20.toJS);
expect(i10.divide(10.toJS), 1.toJS);
expect(i10.modulo(5.toJS), 0.toJS);
expect(i10.exponentiate(2.toJS), 100.toJS);
// Bitwise.
expect(i10.unsignedRightShift(1.toJS), 5.toJS);
// Comparison/relational.
final t = true.toJS;
final f = false.toJS;
// Equality attempts to coerce, whereas strict equality does not.
Expect.isTrue(t.equals(1.toJS));
Expect.isFalse(t.notEquals(1.toJS));
Expect.isFalse(t.strictEquals(1.toJS));
Expect.isTrue(t.strictNotEquals(1.toJS));
Expect.isFalse((t.and(f) as JSBoolean).toDart);
Expect.isTrue((t.or(f) as JSBoolean).toDart);
Expect.isFalse(t.not);
Expect.isTrue(t.isTruthy);
Expect.isFalse(i10.lessThan(i10));
Expect.isTrue(i10.lessThanOrEqualTo(i10));
Expect.isFalse(i10.greaterThan(i10));
Expect.isTrue(i10.greaterThanOrEqualTo(i10));
// Nulls.
expect(null.add(null), 0.toJS);
expect(null.subtract(null), 0.toJS);
expect(null.multiply(null), 0.toJS);
Expect.isTrue(isNaN(null.divide(null)));
Expect.isTrue(isNaN(null.modulo(null)));
expect(null.exponentiate(null), 1.toJS);
expect(null.unsignedRightShift(null), 0.toJS);
Expect.isTrue(null.equals(null));
Expect.isFalse(null.notEquals(null));
Expect.isTrue(null.strictEquals(null));
Expect.isFalse(null.strictNotEquals(null));
expect(null.and(null), null);
expect(null.or(null), null);
Expect.isTrue(null.not);
Expect.isFalse(null.isTruthy);
Expect.isFalse(null.lessThan(null));
Expect.isTrue(null.lessThanOrEqualTo(null));
Expect.isFalse(null.greaterThan(null));
Expect.isTrue(null.greaterThanOrEqualTo(null));
// Different types.
final b10 = 10.toBigInt;
expect(b10.add(1.toBigInt), 11.toBigInt);
expect(b10.subtract(1.toBigInt), 9.toBigInt);
expect(b10.multiply(2.toBigInt), 20.toBigInt);
expect(b10.divide(10.toBigInt), 1.toBigInt);
expect(b10.modulo(5.toBigInt), 0.toBigInt);
expect(b10.exponentiate(2.toBigInt), 100.toBigInt);
// Note that `unsignedRightShift` can not be used with BigInts and always
// returns a number.
expect(t.unsignedRightShift(f), 1.toJS);
final b1 = 1.toBigInt;
Expect.isTrue(b1.equals(t));
Expect.isFalse(b1.notEquals(t));
Expect.isFalse(b1.strictEquals(t));
Expect.isTrue(b1.strictNotEquals(t));
expect(b10.and(b1), b1);
expect(b10.or(b1), b10);
Expect.isFalse(b10.not);
Expect.isTrue(b10.isTruthy);
Expect.isFalse(b10.lessThan(b10));
Expect.isTrue(b10.lessThanOrEqualTo(b10));
Expect.isFalse(b10.greaterThan(b10));
Expect.isTrue(b10.greaterThanOrEqualTo(b10));
}
void main() {
extensionTypeTest();
staticInteropTest();
dartJsInteropOperatorsTest();
}

View file

@ -133,8 +133,10 @@ void instanceOfTest() {
JSFunction jsClass2Constructor = gc['JSClass2'] as JSFunction;
Expect.isTrue(obj.instanceof(jsClass1Constructor));
Expect.isFalse(obj.instanceof(jsClass2Constructor));
Expect.isFalse(0.toJS.instanceof(jsClass2Constructor));
Expect.isTrue(obj.instanceOfString('JSClass1'));
Expect.isFalse(obj.instanceOfString('JSClass2'));
Expect.isFalse(0.toJS.instanceOfString('JSClass1'));
}
void _expectIterableEquals(Iterable<Object?> l, Iterable<Object?> r) {