[dart:js_interop] Add generics to JSArray and JSPromise

In order to support this, adds necessary changes to conversion
functions (including cast-lists) and makes JSArrayImpl a generic
type.

CoreLibraryReviewExempt: Backend-specific library changes.
Change-Id: I58bcfb67ef21c90be5e25735757d780aac52dc23
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/337923
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Srujan Gaddam 2023-12-12 04:50:13 +00:00 committed by Commit Queue
parent c1ba3cf699
commit e6a313568c
10 changed files with 419 additions and 263 deletions

View file

@ -71,6 +71,10 @@
`JSObject.fromInteropObject`. Going forward, it's recommended to use extension
types to define interop APIs. Those extension types can still implement JS
types.
- **JSArray and JSPromise generics**: `JSArray` and `JSPromise` are now generic
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.
[#52687]: https://github.com/dart-lang/sdk/issues/52687

View file

@ -114,12 +114,12 @@ extension ObjectToJSBoxedDartObject on Object {
}
}
/// [JSPromise] -> [Future<JSAny?>].
/// [JSPromise] -> [Future].
@patch
extension JSPromiseToFuture on JSPromise {
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
@patch
@pragma('dart2js:prefer-inline')
Future<JSAny?> get toDart => js_util.promiseToFuture<JSAny?>(this);
Future<T> get toDart => js_util.promiseToFuture<T>(this);
}
/// [JSArrayBuffer] <-> [ByteBuffer]
@ -289,17 +289,22 @@ extension Float64ListToJSFloat64Array on Float64List {
/// [JSArray] <-> [List]
@patch
extension JSArrayToList on JSArray {
extension JSArrayToList<T extends JSAny?> on JSArray<T> {
@patch
@pragma('dart2js:prefer-inline')
List<JSAny?> get toDart => this as List<JSAny?>;
List<T> get toDart {
// Upcast `interceptors.JSArray<Object?>` first to a `List<Object?>` so that
// we only need one type promotion.
List<Object?> t = _jsArray;
return t is List<T> ? t : t.cast<T>();
}
}
@patch
extension ListToJSArray on List<JSAny?> {
extension ListToJSArray<T extends JSAny?> on List<T> {
@patch
@pragma('dart2js:prefer-inline')
JSArray get toJS => this as JSArray;
JSArray<T> get toJS => this as JSArray<T>;
// TODO(srujzs): Should we do a check to make sure this List is a JSArray
// under the hood and then potentially proxy? This applies for user lists. For
@ -311,7 +316,7 @@ extension ListToJSArray on List<JSAny?> {
// check.
@patch
@pragma('dart2js:prefer-inline')
JSArray get toJSProxyOrRef => this as JSArray;
JSArray<T> get toJSProxyOrRef => this as JSArray<T>;
}
/// [JSNumber] -> [double] or [int].

View file

@ -6,35 +6,35 @@ part of dart._js_types;
// TODO(joshualitt): Refactor indexing here and in `js_string` to elide range
// checks for internal functions.
class JSArrayImpl implements List<JSAny?> {
class JSArrayImpl<T extends JSAny?> implements List<T> {
final WasmExternRef? _ref;
JSArrayImpl(this._ref);
factory JSArrayImpl.fromLength(int length) =>
JSArrayImpl(js.newArrayFromLengthRaw(length));
JSArrayImpl<T>(js.newArrayFromLengthRaw(length));
static JSArrayImpl? box(WasmExternRef? ref) =>
js.isDartNull(ref) ? null : JSArrayImpl(ref);
static JSArrayImpl<T>? box<T extends JSAny?>(WasmExternRef? ref) =>
js.isDartNull(ref) ? null : JSArrayImpl<T>(ref);
WasmExternRef? get toExternRef => _ref;
@override
List<R> cast<R>() => List.castFrom<JSAny?, R>(this);
List<R> cast<R>() => List.castFrom<T, R>(this);
@override
void add(JSAny? value) =>
void add(T value) =>
js.JS<void>('(a, i) => a.push(i)', toExternRef, value.toExternRef);
@override
JSAny? removeAt(int index) {
T removeAt(int index) {
RangeError.checkValueInInterval(index, 0, length - 1);
return js.JSValue.boxT<JSAny?>(js.JS<WasmExternRef?>(
return js.JSValue.boxT<T>(js.JS<WasmExternRef?>(
'(a, i) => a.splice(i, 1)[0]', toExternRef, index.toJS.toExternRef));
}
@override
void insert(int index, JSAny? value) {
void insert(int index, T value) {
RangeError.checkValueInInterval(index, 0, length);
js.JS<void>('(a, i, v) => a.splice(i, 0, v)', toExternRef,
index.toJS.toExternRef, value.toExternRef);
@ -44,7 +44,7 @@ class JSArrayImpl implements List<JSAny?> {
'(a, l) => a.length = l', toExternRef, newLength.toJS.toExternRef);
@override
void insertAll(int index, Iterable<JSAny?> iterable) {
void insertAll(int index, Iterable<T> iterable) {
RangeError.checkValueInInterval(index, 0, length);
final that =
iterable is EfficientLengthIterable ? iterable : iterable.toList();
@ -56,7 +56,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
void setAll(int index, Iterable<JSAny?> iterable) {
void setAll(int index, Iterable<T> iterable) {
RangeError.checkValueInInterval(index, 0, length);
for (final element in iterable) {
this[index++] = element;
@ -64,8 +64,8 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
JSAny? removeLast() => js.JSValue.boxT<JSAny?>(
js.JS<WasmExternRef?>('a => a.pop()', toExternRef));
T removeLast() =>
js.JSValue.boxT<T>(js.JS<WasmExternRef?>('a => a.pop()', toExternRef));
@override
bool remove(Object? element) {
@ -80,13 +80,13 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
void removeWhere(bool Function(JSAny?) test) => _retainWhere(test, false);
void removeWhere(bool Function(T) test) => _retainWhere(test, false);
@override
void retainWhere(bool Function(JSAny?) test) => _retainWhere(test, true);
void retainWhere(bool Function(T) test) => _retainWhere(test, true);
void _retainWhere(bool Function(JSAny?) test, bool retainMatching) {
final retained = <JSAny?>[];
void _retainWhere(bool Function(T) test, bool retainMatching) {
final retained = <T>[];
final end = length;
for (var i = 0; i < end; i++) {
final element = this[i];
@ -104,17 +104,17 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
Iterable<JSAny?> where(bool Function(JSAny?) f) {
return WhereIterable<JSAny?>(this, f);
Iterable<T> where(bool Function(T) f) {
return WhereIterable<T>(this, f);
}
@override
Iterable<T> expand<T>(Iterable<T> Function(JSAny?) f) {
return ExpandIterable<JSAny?, T>(this, f);
Iterable<U> expand<U>(Iterable<U> Function(T) f) {
return ExpandIterable<T, U>(this, f);
}
@override
void addAll(Iterable<JSAny?> collection) {
void addAll(Iterable<T> collection) {
for (final v in collection) {
add(v);
}
@ -126,7 +126,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
void forEach(void Function(JSAny?) f) {
void forEach(void Function(T) f) {
final end = length;
for (var i = 0; i < end; i++) {
f(this[i]);
@ -135,8 +135,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
Iterable<T> map<T>(T Function(JSAny?) f) =>
MappedListIterable<JSAny?, T>(this, f);
Iterable<U> map<U>(U Function(T) f) => MappedListIterable<T, U>(this, f);
@override
String join([String separator = ""]) {
@ -152,24 +151,23 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
Iterable<JSAny?> take(int n) => SubListIterable<JSAny?>(this, 0, n);
Iterable<T> take(int n) => SubListIterable<T>(this, 0, n);
@override
Iterable<JSAny?> takeWhile(bool test(JSAny? value)) =>
TakeWhileIterable<JSAny?>(this, test);
Iterable<T> takeWhile(bool test(T value)) => TakeWhileIterable<T>(this, test);
@override
Iterable<JSAny?> skip(int n) => SubListIterable<JSAny?>(this, n, null);
Iterable<T> skip(int n) => SubListIterable<T>(this, n, null);
@override
Iterable<JSAny?> skipWhile(bool Function(JSAny?) test) =>
SkipWhileIterable<JSAny?>(this, test);
Iterable<T> skipWhile(bool Function(T) test) =>
SkipWhileIterable<T>(this, test);
@override
JSAny? reduce(JSAny? combine(JSAny? previousValue, JSAny? element)) {
T reduce(T combine(T previousValue, T element)) {
final end = length;
if (end == 0) throw IterableElementError.noElement();
JSAny? value = this[0];
T value = this[0];
for (var i = 1; i < end; i++) {
final element = this[i];
value = combine(value, element);
@ -179,8 +177,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
T fold<T>(
T initialValue, T Function(T previousValue, JSAny? element) combine) {
U fold<U>(U initialValue, U Function(U previousValue, T element) combine) {
final end = length;
var value = initialValue;
for (int i = 0; i < end; i++) {
@ -192,7 +189,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
JSAny? firstWhere(bool Function(JSAny?) test, {JSAny? Function()? orElse}) {
T firstWhere(bool Function(T) test, {T Function()? orElse}) {
final end = length;
for (int i = 0; i < end; i++) {
final element = this[i];
@ -204,7 +201,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
JSAny? lastWhere(bool Function(JSAny?) test, {JSAny? Function()? orElse}) {
T lastWhere(bool Function(T) test, {T Function()? orElse}) {
final end = length;
for (int i = end - 1; i >= 0; i--) {
final element = this[i];
@ -216,9 +213,9 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
JSAny? singleWhere(bool Function(JSAny?) test, {JSAny? Function()? orElse}) {
T singleWhere(bool Function(T) test, {T Function()? orElse}) {
final end = length;
JSAny? match;
late T match;
var matchFound = false;
for (int i = 0; i < end; i++) {
final element = this[i];
@ -237,35 +234,35 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
JSAny? elementAt(int index) => this[index];
T elementAt(int index) => this[index];
@override
List<JSAny?> sublist(int start, [int? end]) {
List<T> sublist(int start, [int? end]) {
end = RangeError.checkValidRange(start, end, length);
return JSArrayImpl(js.JS<WasmExternRef?>('(a, s, e) => a.slice(s, e)',
return JSArrayImpl<T>(js.JS<WasmExternRef?>('(a, s, e) => a.slice(s, e)',
toExternRef, start.toJS.toExternRef, end.toJS.toExternRef));
}
@override
Iterable<JSAny?> getRange(int start, int end) {
Iterable<T> getRange(int start, int end) {
RangeError.checkValidRange(start, end, length);
return SubListIterable<JSAny?>(this, start, end);
return SubListIterable<T>(this, start, end);
}
@override
JSAny? get first {
T get first {
if (length > 0) return this[0];
throw IterableElementError.noElement();
}
@override
JSAny? get last {
T get last {
if (length > 0) return this[length - 1];
throw IterableElementError.noElement();
}
@override
JSAny? get single {
T get single {
if (length == 1) return this[0];
if (length == 0) throw IterableElementError.noElement();
throw IterableElementError.tooMany();
@ -280,17 +277,16 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
void setRange(int start, int end, Iterable<JSAny?> iterable,
[int skipCount = 0]) {
void setRange(int start, int end, Iterable<T> iterable, [int skipCount = 0]) {
RangeError.checkValidRange(start, end, length);
final rangeLength = end - start;
if (rangeLength == 0) return;
RangeError.checkNotNegative(skipCount);
// TODO(joshualitt): Fast path for when iterable is JS backed.
List<JSAny?> otherList;
List<T> otherList;
int otherStart;
if (iterable is List<JSAny?>) {
if (iterable is List<T>) {
otherList = iterable;
otherStart = skipCount;
} else {
@ -313,15 +309,15 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
void fillRange(int start, int end, [JSAny? fillValue]) {
void fillRange(int start, int end, [T? fillValue]) {
RangeError.checkValidRange(start, end, length);
for (var i = start; i < end; i++) {
this[i] = fillValue;
this[i] = fillValue as T;
}
}
@override
void replaceRange(int start, int end, Iterable<JSAny?> replacement) {
void replaceRange(int start, int end, Iterable<T> replacement) {
RangeError.checkValidRange(start, end, length);
final replacementList = replacement is EfficientLengthIterable
? replacement
@ -348,7 +344,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
bool any(bool test(JSAny? element)) {
bool any(bool test(T element)) {
final end = length;
for (var i = 0; i < end; i++) {
final element = this[i];
@ -359,7 +355,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
bool every(bool test(JSAny? element)) {
bool every(bool test(T element)) {
final end = length;
for (var i = 0; i < end; i++) {
final element = this[i];
@ -370,16 +366,16 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
Iterable<JSAny?> get reversed => ReversedListIterable<JSAny?>(this);
Iterable<T> get reversed => ReversedListIterable<T>(this);
static int _compareAny(JSAny? a, JSAny? b) => js
static int _compareAny<T extends JSAny?>(T a, T b) => js
.JS<double>('(a, b) => a == b ? 0 : (a > b ? 1 : -1)', a.toExternRef,
b.toExternRef)
.toInt();
@override
void sort([int Function(JSAny?, JSAny?)? compare]) =>
Sort.sort(this, compare ?? _compareAny);
void sort([int Function(T, T)? compare]) =>
Sort.sort(this, compare ?? _compareAny<T>);
@override
void shuffle([Random? random]) {
@ -446,14 +442,14 @@ class JSArrayImpl implements List<JSAny?> {
String toString() => ListBase.listToString(this);
@override
List<JSAny?> toList({bool growable = true}) =>
List<JSAny?>.of(this, growable: growable);
List<T> toList({bool growable = true}) =>
List<T>.of(this, growable: growable);
@override
Set<JSAny?> toSet() => Set<JSAny?>.from(this);
Set<T> toSet() => Set<T>.from(this);
@override
Iterator<JSAny?> get iterator => JSArrayImplIterator(this);
Iterator<T> get iterator => JSArrayImplIterator<T>(this);
@override
int get length => js.JS<double>('a => a.length', toExternRef).toInt();
@ -467,45 +463,41 @@ class JSArrayImpl implements List<JSAny?> {
}
@pragma("wasm:prefer-inline")
JSAny? _getUnchecked(int index) =>
js.JSValue.boxT<JSAny?>(js.JS<WasmExternRef?>(
'(a, i) => a[i]', toExternRef, index.toJS.toExternRef));
T _getUnchecked(int index) => js.JSValue.boxT<T>(js.JS<WasmExternRef?>(
'(a, i) => a[i]', toExternRef, index.toJS.toExternRef));
@override
@pragma("wasm:prefer-inline")
JSAny? operator [](int index) {
T operator [](int index) {
IndexErrorUtils.checkAssumePositiveLength(index, length);
return _getUnchecked(index);
}
@pragma("wasm:prefer-inline")
void _setUnchecked(int index, JSAny? value) => js.JS<void>(
'(a, i, v) => a[i] = v',
toExternRef,
index.toJS.toExternRef,
value.toExternRef);
void _setUnchecked(int index, T value) => js.JS<void>('(a, i, v) => a[i] = v',
toExternRef, index.toJS.toExternRef, value.toExternRef);
@override
@pragma("wasm:prefer-inline")
void operator []=(int index, JSAny? value) {
void operator []=(int index, T value) {
IndexErrorUtils.checkAssumePositiveLength(index, length);
_setUnchecked(index, value);
}
@override
Map<int, JSAny?> asMap() => ListMapView<JSAny?>(this);
Map<int, T> asMap() => ListMapView<T>(this);
@override
Iterable<JSAny?> followedBy(Iterable<JSAny?> other) =>
FollowedByIterable<JSAny?>.firstEfficient(this, other);
Iterable<T> followedBy(Iterable<T> other) =>
FollowedByIterable<T>.firstEfficient(this, other);
@override
Iterable<T> whereType<T>() => WhereTypeIterable<T>(this);
@override
List<JSAny?> operator +(List<JSAny?> other) {
List<T> operator +(List<T> other) {
if (other is JSArrayImpl) {
return JSArrayImpl(js.JS<WasmExternRef?>(
return JSArrayImpl<T>(js.JS<WasmExternRef?>(
'(a, t) => a.concat(t)', toExternRef, other.toExternRef));
} else {
return [...this, ...other];
@ -513,7 +505,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
int indexWhere(bool Function(JSAny?) test, [int start = 0]) {
int indexWhere(bool Function(T) test, [int start = 0]) {
if (start >= length) {
return -1;
}
@ -529,7 +521,7 @@ class JSArrayImpl implements List<JSAny?> {
}
@override
int lastIndexWhere(bool Function(JSAny?) test, [int? start]) {
int lastIndexWhere(bool Function(T) test, [int? start]) {
if (start == null) {
start = length - 1;
}
@ -544,14 +536,14 @@ class JSArrayImpl implements List<JSAny?> {
return -1;
}
void set first(JSAny? element) {
void set first(T element) {
if (isEmpty) {
throw IterableElementError.noElement();
}
this[0] = element;
}
void set last(JSAny? element) {
void set last(T element) {
if (isEmpty) {
throw IterableElementError.noElement();
}
@ -561,14 +553,14 @@ class JSArrayImpl implements List<JSAny?> {
// TODO(joshualitt): Override hash code and operator==?
}
class JSArrayImplIterator implements Iterator<JSAny?> {
final JSArrayImpl _array;
class JSArrayImplIterator<T extends JSAny?> implements Iterator<T> {
final JSArrayImpl<T> _array;
final int _length;
int _index = -1;
JSArrayImplIterator(this._array) : _length = _array.length {}
JSAny? get current => _array[_index];
T get current => _array[_index];
bool moveNext() {
if (_length != _array.length) {

View file

@ -65,6 +65,10 @@ class JSValue {
WasmExternRef? get toExternRef => _ref;
}
// Extension helpers to convert to an externref.
// TODO(srujzs): We should rename these to `getAsExternRef` so they don't
// collide with instance members of box objects.
extension DoubleToExternRef on double? {
WasmExternRef? get toExternRef =>
this == null ? WasmExternRef.nullRef : toJSNumber(this!);
@ -527,9 +531,9 @@ List<int> jsIntTypedArrayToDartIntTypedData(
return list;
}
JSArray toJSArray(List<JSAny?> list) {
JSArray<T> toJSArray<T extends JSAny?>(List<T> list) {
int length = list.length;
JSArray result = JSArray.withLength(length);
JSArray<T> result = JSArray<T>.withLength(length);
for (int i = 0; i < length; i++) {
result[i.toJS] = list[i];
}

View file

@ -88,7 +88,9 @@ extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
@patch
extension FunctionToJSExportedDartFunction on Function {
@patch
JSExportedDartFunction get toJS => throw UnimplementedError();
JSExportedDartFunction get toJS => throw UnimplementedError(
'This should never be called. Calls to toJS should have been transformed '
'by the interop transformer.');
}
/// [JSBoxedDartObject] <-> [Object]
@ -109,14 +111,23 @@ extension ObjectToJSBoxedDartObject on Object {
}
}
/// [JSPromise] -> [Future<JSAny?>].
/// [JSPromise] -> [Future].
@patch
extension JSPromiseToFuture on JSPromise {
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
@patch
Future<JSAny?> get toDart {
final completer = Completer<JSAny?>();
Future<T> get toDart {
final completer = Completer<T>();
final success = (JSAny? r) {
return completer.complete(r);
// Note that we explicitly type the parameter as `JSAny?` instead of `T`.
// This is because if there's a `TypeError` with the cast, we want to
// bubble that up through the completer, so we end up doing a try-catch
// here to do so.
try {
final value = r as T;
completer.complete(value);
} catch (e) {
completer.completeError(e);
}
}.toJS;
final error = (JSAny? e) {
// TODO(joshualitt): Investigate reifying `JSNull` and `JSUndefined` on
@ -127,9 +138,10 @@ extension JSPromiseToFuture on JSPromise {
if (e == null) {
// Note that we pass false as a default. It's not currently possible to
// be able to differentiate between null and undefined.
return completer.completeError(js_util.NullRejectionException(false));
completer.completeError(js_util.NullRejectionException(false));
return;
}
return completer.completeError(e);
completer.completeError(e);
}.toJS;
promiseThen(toExternRef, success.toExternRef, error.toExternRef);
return completer.future;
@ -341,25 +353,27 @@ extension Float64ListToJSFloat64Array on Float64List {
/// [JSArray] <-> [List]
@patch
extension JSArrayToList on JSArray {
extension JSArrayToList<T extends JSAny?> on JSArray<T> {
@patch
List<JSAny?> get toDart => js_types.JSArrayImpl(toExternRef);
List<T> get toDart => js_types.JSArrayImpl<T>(toExternRef);
}
@patch
extension ListToJSArray on List<JSAny?> {
JSArray? get _underlyingArray {
extension ListToJSArray<T extends JSAny?> on List<T> {
JSArray<T>? get _underlyingArray {
final t = this;
return t is js_types.JSArrayImpl
? JSValue.boxT<JSArray>(t.toExternRef)
// Explicit cast to avoid using the extension method.
? JSValue.boxT<JSArray<T>>((t as js_types.JSArrayImpl).toExternRef)
: null;
}
@patch
JSArray get toJS => _underlyingArray ?? toJSArray(this);
JSArray<T> get toJS => _underlyingArray ?? toJSArray<T>(this);
@patch
JSArray get toJSProxyOrRef => _underlyingArray ?? _createJSProxyOfList(this);
JSArray<T> get toJSProxyOrRef =>
_underlyingArray ?? _createJSProxyOfList<T>(this);
}
/// [JSNumber] -> [double] or [int].
@ -481,7 +495,7 @@ class _ListBackedJSArray {
}
}
JSArray _createJSProxyOfList(List<JSAny?> list) {
JSArray<T> _createJSProxyOfList<T extends JSAny?>(List<T> list) {
final wrapper = _ListBackedJSArray(list);
final jsExportWrapper =
js_util.createStaticInteropMock<__ListBackedJSArray, _ListBackedJSArray>(
@ -497,7 +511,7 @@ JSArray _createJSProxyOfList(List<JSAny?> list) {
final hasIndex = jsExportWrapper['_hasIndex']!.toExternRef;
final deleteIndex = jsExportWrapper['_deleteIndex']!.toExternRef;
final proxy = _box<JSArray>(js_helper.JS<WasmExternRef?>('''
final proxy = _box<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

@ -100,19 +100,45 @@ extension type JSExportedDartFunction._(
JSExportedDartFunctionRepType _jsExportedDartFunction)
implements JSFunction {}
/// The type of JS promises and promise-like objects.
@JS('Promise')
extension type JSPromise._(JSPromiseRepType _jsPromise) implements JSObject {
external JSPromise(JSFunction executor);
}
/// The type of all JS arrays.
///
/// Because [JSArray] is an extension type, [T] is only a static guarantee and
/// the array does not necessarily only contain [T] elements. For example:
///
/// ```
/// @JS()
/// external JSArray<JSNumber> get array;
/// ```
///
/// We do not check that `array` actually has [JSNumber]s when calling this
/// member. The only check is that `array` is a [JSArrayRepType].
///
/// [T] may introduce additional checking elsewhere, however. When accessing
/// elements of [JSArray] with type [T], there is a check to ensure the element
/// is a [T] to ensure soundness. Similarly, when converting to a [List<T>],
/// casts may be introduced to ensure that it is indeed a [List<T>].
@JS('Array')
extension type JSArray._(JSArrayRepType _jsArray) implements JSObject {
extension type JSArray<T extends JSAny?>._(JSArrayRepType _jsArray)
implements JSObject {
external JSArray();
external JSArray.withLength(int length);
}
/// The type of JS promises and promise-like objects.
///
/// Because [JSPromise] is an extension type, [T] is only a static guarantee and
/// the [JSPromise] may not actually resolve to a [T]. Like with [JSArray], we
/// only check that this is a [JSPromiseRepType].
///
/// Also like with [JSArray], [T] may introduce additional checking elsewhere.
/// When converted to a [Future<T>], there is a cast to ensure that the [Future]
/// actually resolves to a [T] to ensure soundness.
@JS('Promise')
extension type JSPromise<T extends JSAny?>._(JSPromiseRepType _jsPromise)
implements JSObject {
external JSPromise(JSFunction executor);
}
/// The type of the boxed Dart object that can be passed to JS safely. There is
/// no interface specified of this boxed object, and you may get a new box each
/// time you box the same Dart object.
@ -291,14 +317,14 @@ extension ObjectToJSBoxedDartObject on Object {
external JSBoxedDartObject get toJSBox;
}
/// [JSPromise] -> [Future<JSAny?>].
extension JSPromiseToFuture on JSPromise {
external Future<JSAny?> get toDart;
/// [JSPromise] -> [Future].
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
external Future<T> get toDart;
}
extension FutureOfJSAnyToJSPromise on Future<JSAny?> {
JSPromise get toJS {
return JSPromise((JSFunction resolve, JSFunction reject) {
extension FutureOfJSAnyToJSPromise<T extends JSAny?> on Future<T> {
JSPromise<T> get toJS {
return JSPromise<T>((JSFunction resolve, JSFunction reject) {
this.then((JSAny? value) {
resolve.callAsFunction(resolve, value);
return value;
@ -454,20 +480,20 @@ extension Float64ListToJSFloat64Array on Float64List {
}
/// [JSArray] <-> [List]
extension JSArrayToList on JSArray {
extension JSArrayToList<T extends JSAny?> on JSArray<T> {
/// Returns a list wrapper of the JS array.
///
/// Modifying the JS array will modify the returned list and vice versa.
external List<JSAny?> get toDart;
external List<T> get toDart;
}
extension ListToJSArray on List<JSAny?> {
extension ListToJSArray<T extends JSAny?> on List<T> {
/// Compiler-specific conversion from list to JS array.
///
/// This is either a pass-by-reference, unwrap, or copy depending on the
/// implementation of the given list, and users shouldn't rely on
/// modifications to the list to affect the array or vice versa.
external JSArray get toJS;
external JSArray<T> get toJS;
/// Either passes by reference, unwraps, or creates a heavyweight proxy that
/// wraps the list.
@ -477,7 +503,7 @@ extension ListToJSArray on List<JSAny?> {
/// by reference and dart2wasm will add a proxy or unwrap for most lists.
///
/// **WARNING**: Do not rely on this to be performant.
external JSArray get toJSProxyOrRef;
external JSArray<T> get toJSProxyOrRef;
}
/// [JSNumber] -> [double] or [int].

View file

@ -59,8 +59,8 @@ class InvalidCovariant {
void main() {
createStaticInteropMock<
//^
// [web] Type argument 'Params<JSArray, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
// [web] Type argument 'ParamsImpl<JSArray, JSArray, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
// [web] Type argument 'Params<JSArray<JSAny?>, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
// [web] Type argument 'ParamsImpl<JSArray<JSAny?>, JSArray<JSAny?>, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
Params<JSArray, Params>,
ParamsImpl<JSArray, JSArray, Params>>(
ParamsImpl<JSArray, JSArray, Params>());

View file

@ -10,12 +10,12 @@ import 'dart:js_interop';
import 'package:expect/expect.dart';
// We run many tests in three configurations:
// 1) Test should ensure receivers
// for all [List] operations will be `JSArrayImpl`.
// 1) Test should ensure receivers for all [List] operations will be
// `JSArrayImpl`.
// 2) Test should ensure arguments to all [List] operations will be
// `JSArrayImpl`.
// 3) Test should ensure both receivers and arguments for all [List]
// operations will be `JSArrayImpl`.
// `JSArrayImpl`.
// 3) Test should ensure both receivers and arguments for all [List] operations
// will be `JSArrayImpl`.
enum TestMode {
jsReceiver,
jsArgument,
@ -35,13 +35,11 @@ bool useJSType(Position pos, TestMode mode) =>
(mode == TestMode.jsArgument ||
mode == TestMode.jsReceiverAndArguments));
late bool testProxiedArray;
// We test two types of round-trips from Dart to JS to Dart:
// - A copy that `toJS` creates that then gets wrapped by JSArrayImpl
// - A proxy that `toJSProxyOrRef` creates that then gets wrapped by JSArrayImpl
List<JSAny?> jsList(List<JSAny?> l) {
final arr = testProxiedArray ? l.toJS : l.toJSProxyOrRef;
List<T> jsList<T extends JSAny?>(List<T> l, bool testProxy) {
final arr = testProxy ? l.toJS : l.toJSProxyOrRef;
return arr.toDart;
}
@ -53,13 +51,11 @@ extension ListJSAnyExtension on List<JSAny?> {
}
extension ListNumExtension on List<num?> {
List<JSAny?> get toListJSAny => this.map((n) => n?.toJS).toList();
List<JSAny?> get toJSListJSAny => jsList(this.map((n) => n?.toJS).toList());
}
List<T> toListT<T extends JSAny?>() =>
this.map<T>((n) => n?.toJS as T).toList();
extension ListStringExtension on List<String?> {
List<JSAny?> get toListJSAny => this.map((n) => n?.toJS).toList();
List<JSAny?> get toJSListJSAny => jsList(this.map((n) => n?.toJS).toList());
List<T> toJSListT<T extends JSAny?>(bool testProxy) =>
jsList<T>(this.toListT<T>(), testProxy);
}
extension NullableJSAnyExtension on JSAny? {
@ -70,12 +66,19 @@ extension JSAnyExtension on JSAny {
double get toDouble => (this as JSNumber).toDartDouble;
}
void modedTests(TestMode mode) {
List<JSAny?> rListDouble(List<double> l) =>
useJSType(Position.jsReceiver, mode) ? l.toJSListJSAny : l.toListJSAny;
// Test the list methods that need to take in a list using the `mode` to
// indicate whether the receiver or the argument or both are `JSArrayImpl` on
// dart2wasm. `T` here is the type of the list to use (either `JSAny?` or
// `JSArray?`) and `testProxy` determines whether we do a round trip conversion
// using a potentially proxied list or the default `toJS` conversion.
void modedTests<T extends JSAny?>(TestMode mode, {required bool testProxy}) {
List<T> rListDouble(List<double> l) => useJSType(Position.jsReceiver, mode)
? l.toJSListT<T>(testProxy)
: l.toListT<T>();
List<JSAny?> aListDouble(List<double> l) =>
useJSType(Position.jsArgument, mode) ? l.toJSListJSAny : l.toListJSAny;
List<T> aListDouble(List<double> l) => useJSType(Position.jsArgument, mode)
? l.toJSListT<T>(testProxy)
: l.toListT<T>();
var rlist = rListDouble([1, 2, 3, 4]);
@ -153,18 +156,25 @@ void modedTests(TestMode mode) {
Expect.listEquals([1, 2], (rlist + alist).toListDouble);
}
void nonModedTests() {
var list = [1, 2, 3, 4].toJSListJSAny;
// Test the list methods that don't need to take in a list, and therefore the
// mode does not matter. `T` here is the type of the list to use (either
// `JSAny?` or `JSArray?`) and `testProxy` determines whether we do a
// round trip conversion using a potentially proxied list or the default
// `toJS` conversion.
void nonModedTests<T extends JSAny?>({required bool testProxy}) {
List<T> toJSList(List<num?> l) => l.toJSListT<T>(testProxy);
var list = toJSList([1, 2, 3, 4]);
// iteration
var count = 0;
for (var _ in <num>[].toJSListJSAny) {
for (var _ in toJSList(<num>[])) {
Expect.equals(true, false);
count++;
}
Expect.equals(0, count);
for (var i in [1].toJSListJSAny) {
for (var i in toJSList([1])) {
Expect.equals(1, i?.toDouble);
count++;
}
@ -187,54 +197,54 @@ void nonModedTests() {
// operators [], []=
Expect.equals(1, list[0]?.toDouble);
list[0] = 5.toJS;
list[0] = 5.toJS as T;
Expect.equals(5, list[0]?.toDouble);
list[0] = null;
list[0] = null as T;
Expect.equals(null, list[0]);
// indexOf, lastIndexOf
list = [0, 1, 2, 3].toJSListJSAny;
list = toJSList([0, 1, 2, 3]);
for (var i = 0; i < 4; i++) {
Expect.equals(i, list[i]?.toDouble);
Expect.equals(i, list.indexOf(i.toJS));
Expect.equals(i, list.lastIndexOf(i.toJS));
Expect.equals(i, list.indexOf(i.toJS as T));
Expect.equals(i, list.lastIndexOf(i.toJS as T));
}
// fillRange
list = [3, 3, 3, 1].toJSListJSAny;
list = toJSList([3, 3, 3, 1]);
list.fillRange(1, 3);
Expect.listEquals([3, null, null, 1], list.toListDouble);
list.fillRange(1, 3, 7.toJS);
list.fillRange(1, 3, 7.toJS as T);
Expect.listEquals([3, 7, 7, 1], list.toListDouble);
list.fillRange(0, 0, 9.toJS);
list.fillRange(0, 0, 9.toJS as T);
Expect.listEquals([3, 7, 7, 1], list.toListDouble);
list.fillRange(4, 4, 9.toJS);
list.fillRange(4, 4, 9.toJS as T);
Expect.listEquals([3, 7, 7, 1], list.toListDouble);
list.fillRange(0, 4, 9.toJS);
list.fillRange(0, 4, 9.toJS as T);
Expect.listEquals([9, 9, 9, 9], list.toListDouble);
// sort
list.setRange(0, 4, [3, 2, 1, 0].toJSListJSAny);
list.setRange(0, 4, toJSList([3, 2, 1, 0]));
list.sort();
Expect.listEquals([0, 1, 2, 3], list.toListDouble);
// Iterable methods
list = [0, 1, 2, 3].toJSListJSAny;
list = toJSList([0, 1, 2, 3]);
Expect.listEquals([0, 2, 4, 6], list.map((v) => v!.toDouble * 2).toList());
bool matchAll(JSAny? _) => true;
bool matchNone(JSAny? _) => false;
bool matchSome(JSAny? v) {
bool matchAll(T _) => true;
bool matchNone(T _) => false;
bool matchSome(T v) {
double d = v!.toDouble;
return d == 1 || d == 2;
}
bool matchFirst(JSAny? v) => v!.toDouble == 0;
bool matchLast(JSAny? v) => v!.toDouble == 3;
bool matchFirst(T v) => v!.toDouble == 0;
bool matchLast(T v) => v!.toDouble == 3;
// where
Expect.listEquals([1, 2], list.where(matchSome).toList().toListDouble);
@ -255,38 +265,38 @@ void nonModedTests() {
list.clear();
Expect.equals(0, list.length);
Expect.isTrue(list.isEmpty);
list.add(4.toJS);
list.add(4.toJS as T);
Expect.isTrue(list.isNotEmpty);
Expect.equals(1, list.length);
Expect.equals(4, list.removeLast()?.toDouble);
Expect.equals(0, list.length);
list.add(null);
list.add(null as T);
Expect.equals(null, list.removeLast());
// remove
list = [1, 2, 3, 4, 4].toJSListJSAny;
list = toJSList([1, 2, 3, 4, 4]);
Expect.isTrue(list.remove(4.toJS));
Expect.listEquals([1, 2, 3, 4], list.toListDouble);
// removeWhere
list = [1, 2, 3, 4].toJSListJSAny;
list.removeWhere((JSAny? v) => v!.toDouble % 2 == 0);
list = toJSList([1, 2, 3, 4]);
list.removeWhere((T v) => v!.toDouble % 2 == 0);
Expect.listEquals([1, 3], list.toListDouble);
// retainWhere
list = [1, 2, 3, 4].toJSListJSAny;
list.retainWhere((JSAny? v) => v!.toDouble % 2 == 0);
list = toJSList([1, 2, 3, 4]);
list.retainWhere((T v) => v!.toDouble % 2 == 0);
Expect.listEquals([2, 4], list.toListDouble);
// insert
list.clear();
list.insert(0, 0.toJS);
list.insert(0, 0.toJS as T);
Expect.listEquals([0], list.toListDouble);
list.insert(0, 1.toJS);
list.insert(0, 1.toJS as T);
Expect.listEquals([1, 0], list.toListDouble);
list.insert(2, 2.toJS);
list.insert(2, 2.toJS as T);
Expect.listEquals([1, 0, 2], list.toListDouble);
list.insert(0, null);
list.insert(0, null as T);
Expect.listEquals([null, 1, 0, 2], list.toListDouble);
// removeAt
@ -296,11 +306,11 @@ void nonModedTests() {
Expect.listEquals([1, 2], list.toListDouble);
// reversed
list = [1, 2, 3, 4, 5, 6].toJSListJSAny;
list = toJSList([1, 2, 3, 4, 5, 6]);
Expect.listEquals([6, 5, 4, 3, 2, 1], list.reversed.toList().toListDouble);
// forEach
list = [1, 2, 3].toJSListJSAny;
list = toJSList([1, 2, 3]);
index = 0;
list.forEach((v) {
index++;
@ -309,18 +319,18 @@ void nonModedTests() {
Expect.equals(index, list.length);
// join
list = ['a', 'b', 'c'].toJSListJSAny;
Expect.equals('a,b,c', list.join(','));
Expect.equals('a,b,c', list.join(jsString(',')));
list = toJSList([0, 1, 2]);
Expect.equals('0,1,2', list.join(','));
Expect.equals('0,1,2', list.join(jsString(',')));
Expect.equals('abc', list.join());
Expect.equals('012', list.join());
list = <String>[].toJSListJSAny;
list = toJSList(<int>[]);
Expect.equals('', list.join(','));
Expect.equals('', list.join(jsString(',')));
// take
list = [1, 2, 3].toJSListJSAny;
list = toJSList([1, 2, 3]);
Expect.listEquals([], list.take(0).toList().toListDouble);
Expect.listEquals([1], list.take(1).toList().toListDouble);
@ -357,8 +367,8 @@ void nonModedTests() {
Expect.equals(
6,
(list.reduce((a, b) =>
((a as JSNumber).toDouble + (b as JSNumber).toDouble).toJS)
as JSNumber)
((a as JSNumber).toDouble + (b as JSNumber).toDouble).toJS
as T) as JSNumber)
.toDartDouble);
// fold
@ -372,18 +382,18 @@ void nonModedTests() {
45,
list
.firstWhere((a) => (a as JSNumber).toDartDouble == 4,
orElse: () => 45.toJS)
orElse: () => 45.toJS as T)
.toDouble);
// lastWhere
list = [1, 2, 3, 4].toJSListJSAny;
list = toJSList([1, 2, 3, 4]);
Expect.equals(
4, list.lastWhere((a) => (a as JSNumber).toDartDouble % 2 == 0).toDouble);
Expect.equals(
45,
list
.lastWhere((a) => (a as JSNumber).toDartDouble == 5,
orElse: () => 45.toJS)
orElse: () => 45.toJS as T)
.toDouble);
// singleWhere
@ -395,7 +405,7 @@ void nonModedTests() {
45,
list
.singleWhere((a) => (a as JSNumber).toDartDouble == 5,
orElse: () => 45.toJS)
orElse: () => 45.toJS as T)
.toDouble);
// sublist
@ -415,24 +425,24 @@ void nonModedTests() {
list.removeRange(0, 4);
Expect.listEquals([], list.toListDouble);
list = [1, 2, 3, 4].toJSListJSAny;
list = toJSList([1, 2, 3, 4]);
list.removeRange(1, 4);
Expect.listEquals([1], list.toListDouble);
list = [1, 2, 3, 4].toJSListJSAny;
list = toJSList([1, 2, 3, 4]);
list.removeRange(1, 3);
Expect.listEquals([1, 4], list.toListDouble);
list = [1, 2, 3, 4].toJSListJSAny;
list = toJSList([1, 2, 3, 4]);
list.removeRange(0, 3);
Expect.listEquals([4], list.toListDouble);
// shuffle
list = [1, 2, 3, 4].toJSListJSAny;
list = toJSList([1, 2, 3, 4]);
list.shuffle(MockRandom(4));
Expect.listEquals([4, 2, 3, 1], list.toListDouble);
list = [1, 2, 3, 4].toJSListJSAny;
list = toJSList([1, 2, 3, 4]);
list.shuffle();
Expect.equals(4, list.length);
Expect.isTrue(list.contains(1.toJS));
@ -447,15 +457,18 @@ void runAllTests() {
TestMode.jsArgument,
TestMode.jsReceiverAndArguments
]) {
modedTests(mode);
modedTests<JSAny?>(mode, testProxy: true);
modedTests<JSAny?>(mode, testProxy: false);
modedTests<JSNumber?>(mode, testProxy: true);
modedTests<JSNumber?>(mode, testProxy: false);
}
nonModedTests();
nonModedTests<JSAny?>(testProxy: true);
nonModedTests<JSAny?>(testProxy: false);
nonModedTests<JSNumber?>(testProxy: true);
nonModedTests<JSNumber?>(testProxy: false);
}
void main() {
testProxiedArray = false;
runAllTests();
testProxiedArray = true;
runAllTests();
}

View file

@ -42,142 +42,124 @@ void main() {
// [JSBoxedDartObject] != [Object]
((JSBoxedDartObject jsObj) {})(DartObject());
// ^
// [web] The argument type 'DartObject' can't be assigned to the parameter type 'JSBoxedDartObject'.
// ^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'DartObject' can't be assigned to the parameter type 'JSBoxedDartObject'.
// [JSArray] != [List<JSAny?>]
List<JSAny?> dartArr = <JSAny?>[1.0.toJS, 'foo'.toJS];
((JSArray jsArr) {})(dartArr);
// ^
// [web] The argument type 'List<JSAny?>' can't be assigned to the parameter type 'JSArray'.
// ^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'List<JSAny?>' can't be assigned to the parameter type 'JSArray<JSAny?>'.
// [JSArrayBuffer] != [ByteBuffer]
ByteBuffer dartBuf = Uint8List.fromList([0, 255, 0, 255]).buffer;
((JSArrayBuffer jsBuf) {})(dartBuf);
// ^
// [web] The argument type 'ByteBuffer' can't be assigned to the parameter type 'JSArrayBuffer'.
// ^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'ByteBuffer' can't be assigned to the parameter type 'JSArrayBuffer'.
// [JSDataView] != [ByteData]
ByteData dartDat = Uint8List.fromList([0, 255, 0, 255]).buffer.asByteData();
((JSDataView jsDat) {})(dartDat);
// ^
// [web] The argument type 'ByteData' can't be assigned to the parameter type 'JSDataView'.
// ^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'ByteData' can't be assigned to the parameter type 'JSDataView'.
// [JSTypedArray]s != [TypedData]s
TypedData typedData = Int8List.fromList([-128, 0, 127]);
((JSTypedArray jsTypedArray) {})(typedData);
// ^
// [web] The argument type 'TypedData' can't be assigned to the parameter type 'JSTypedArray'.
// ^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'TypedData' can't be assigned to the parameter type 'JSTypedArray'.
// [JSInt8Array]s != [Int8List]s
Int8List ai8 = Int8List.fromList([-128, 0, 127]);
((JSInt8Array jsAi8) {})(ai8);
// ^
// [web] The argument type 'Int8List' can't be assigned to the parameter type 'JSInt8Array'.
// ^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Int8List' can't be assigned to the parameter type 'JSInt8Array'.
// [JSUint8Array] != [Uint8List]
Uint8List au8 = Uint8List.fromList([-1, 0, 255, 256]);
((JSUint8Array jsAu8) {})(au8);
// ^
// [web] The argument type 'Uint8List' can't be assigned to the parameter type 'JSUint8Array'.
// ^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Uint8List' can't be assigned to the parameter type 'JSUint8Array'.
// [JSUint8ClampedArray] != [Uint8ClampedList]
Uint8ClampedList ac8 = Uint8ClampedList.fromList([-1, 0, 255, 256]);
((JSUint8ClampedArray jsAc8) {})(ac8);
// ^
// [web] The argument type 'Uint8ClampedList' can't be assigned to the parameter type 'JSUint8ClampedArray'.
// ^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Uint8ClampedList' can't be assigned to the parameter type 'JSUint8ClampedArray'.
// [JSInt16Array] != [Int16List]
Int16List ai16 = Int16List.fromList([-32769, -32768, 0, 32767, 32768]);
((JSInt16Array jsAi16) {})(ai16);
// ^
// [web] The argument type 'Int16List' can't be assigned to the parameter type 'JSInt16Array'.
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Int16List' can't be assigned to the parameter type 'JSInt16Array'.
// [JSUint16Array] != [Uint16List]
Uint16List au16 = Uint16List.fromList([-1, 0, 65535, 65536]);
((JSUint16Array jsAu16) {})(au16);
// ^
// [web] The argument type 'Uint16List' can't be assigned to the parameter type 'JSUint16Array'.
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Uint16List' can't be assigned to the parameter type 'JSUint16Array'.
// [JSInt32Array] != [Int32List]
Int32List ai32 = Int32List.fromList([-2147483648, 0, 2147483647]);
((JSInt32Array jsAi32) {})(ai32);
// ^
// [web] The argument type 'Int32List' can't be assigned to the parameter type 'JSInt32Array'.
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Int32List' can't be assigned to the parameter type 'JSInt32Array'.
// [JSUint32Array] != [Uint32List]
Uint32List au32 = Uint32List.fromList([-1, 0, 4294967295, 4294967296]);
((JSUint32Array jsAu32) {})(au32);
// ^
// [web] The argument type 'Uint32List' can't be assigned to the parameter type 'JSUint32Array'.
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Uint32List' can't be assigned to the parameter type 'JSUint32Array'.
// [JSFloat32Array] != [Float32List]
Float32List af32 =
Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
((JSFloat32Array jsAf32) {})(af32);
// ^
// [web] The argument type 'Float32List' can't be assigned to the parameter type 'JSFloat32Array'.
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Float32List' can't be assigned to the parameter type 'JSFloat32Array'.
// [JSFloat64Array] != [Float64List]
Float64List af64 =
Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
((JSFloat64Array jsAf64) {})(af64);
// ^
// [web] The argument type 'Float64List' can't be assigned to the parameter type 'JSFloat64Array'.
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Float64List' can't be assigned to the parameter type 'JSFloat64Array'.
// [JSNumber] != [double]
((JSNumber jsNum) {})(4.5);
// ^
// [web] The argument type 'double' can't be assigned to the parameter type 'JSNumber'.
// ^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'double' can't be assigned to the parameter type 'JSNumber'.
// [JSBoolean] != [bool]
((JSBoolean jsBool) {})(true);
// ^
// [web] The argument type 'bool' can't be assigned to the parameter type 'JSBoolean'.
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'bool' can't be assigned to the parameter type 'JSBoolean'.
// [JSString] != [String]
((JSString jsStr) {})('foo');
// ^
// [web] The argument type 'String' can't be assigned to the parameter type 'JSString'.
// ^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'String' can't be assigned to the parameter type 'JSString'.
// [JSPromise] != [Future]
((JSPromise promise) {})(Future<void>.delayed(Duration.zero));
// ^
// [web] The argument type 'Future<void>' can't be assigned to the parameter type 'JSPromise'.
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_TYPE_NOT_ASSIGNABLE
// [web] The argument type 'Future<void>' can't be assigned to the parameter type 'JSPromise<JSAny?>'.
}

View file

@ -43,6 +43,9 @@ external JSExportedDartFunction edf;
@JS()
external JSArray arr;
@JS('arr')
external JSArray<JSNumber> arrN;
@JS()
external JSBoxedDartObject edo;
@ -179,13 +182,46 @@ void syncTests() {
Expect.throws(() => edo.toJSBox);
// [JSArray] <-> [List<JSAny?>]
arr = [1.0.toJS, 'foo'.toJS].toJS;
final list = <JSAny?>[1.0.toJS, 'foo'.toJS];
arr = list.toJS;
expect(arr is JSArray, true);
expect(confuse(arr) is JSArray, true);
List<JSAny?> dartArr = arr.toDart;
expect((dartArr[0] as JSNumber).toDartDouble, 1.0);
expect((dartArr[1] as JSString).toDart, 'foo');
List<JSNumber> dartArrN = arrN.toDart;
if (isJSBackend) {
// Since lists on the JS backends are passed by ref, we only create a
// cast-list if there's a downcast needed.
expect(dartArr, list);
Expect.notEquals(dartArrN, list);
Expect.throwsTypeError(() => dartArrN[1]);
} else {
// On dart2wasm, we always create a new list using JSArrayImpl.
Expect.notEquals(dartArr, list);
Expect.notEquals(dartArrN, list);
dartArrN[1];
}
// [JSArray<T>] <-> [List<T>]
final listN = <JSNumber>[1.0.toJS, 2.0.toJS];
arrN = listN.toJS;
expect(arrN is JSArray<JSNumber>, true);
expect(confuse(arrN) is JSArray<JSNumber>, true);
dartArr = arr.toDart;
dartArrN = arrN.toDart;
if (isJSBackend) {
// A cast-list should not be introduced if the the array is already the
// right list type.
expect(dartArr, listN);
expect(dartArrN, listN);
} else {
Expect.notEquals(dartArr, list);
Expect.notEquals(dartArrN, list);
}
// [ArrayBuffer] <-> [ByteBuffer]
buf = Uint8List.fromList([0, 255, 0, 255]).buffer.toJS;
expect(buf is JSArrayBuffer, true);
@ -329,16 +365,18 @@ void syncTests() {
}
@JS()
external JSPromise getResolvedPromise();
external JSPromise<T> getResolvedPromise<T extends JSAny?>();
@JS()
external JSPromise getRejectedPromise();
external JSPromise<T> getRejectedPromise<T extends JSAny?>();
@JS()
external JSPromise resolvePromiseWithNullOrUndefined(bool resolveWithNull);
external JSPromise<T> resolvePromiseWithNullOrUndefined<T extends JSAny?>(
bool resolveWithNull);
@JS()
external JSPromise rejectPromiseWithNullOrUndefined(bool resolveWithNull);
external JSPromise<T> rejectPromiseWithNullOrUndefined<T extends JSAny?>(
bool resolveWithNull);
Future<void> asyncTests() async {
eval(r'''
@ -359,10 +397,32 @@ Future<void> asyncTests() async {
// [JSPromise] -> [Future].
// Test resolution.
{
Future<JSAny?> f = getResolvedPromise().toDart;
final f = getResolvedPromise().toDart;
expect(((await f) as JSString).toDart, 'resolved');
}
// Test resolution with generics.
{
final f = getResolvedPromise<JSString>().toDart;
expect((await f).toDart, 'resolved');
}
// Test resolution with incorrect type.
// TODO(54214): This type error is not caught in the JS compilers correctly.
// {
// try {
// final f = getResolvedPromise<JSNumber>().toDart;
// final jsNum = await f;
// // TODO(54179): This should be a `jsNum.toDart` call, but currently we try
// // to coerce all extern refs into primitive types in this conversion
// // method. Change this once that bug is fixed.
// if (!jsNum.typeofEquals('number')) throw TypeError();
// fail('Expected resolution or use of type to throw.');
// } catch (e) {
// expect(e is TypeError, true);
// }
// }
// Test rejection.
{
try {
@ -374,10 +434,21 @@ Future<void> asyncTests() async {
}
}
// Test rejection with generics.
{
try {
await getRejectedPromise<JSString>().toDart;
fail('Expected rejected promise to throw.');
} catch (e) {
final jsError = e as JSObject;
expect(jsError.toString(), 'Error: rejected');
}
}
// Test resolution Promise chaining.
{
bool didThen = false;
Future<JSAny?> f = getResolvedPromise().toDart.then((resolved) {
final f = getResolvedPromise().toDart.then((resolved) {
expect((resolved as JSString).toDart, 'resolved');
didThen = true;
return null;
@ -388,7 +459,7 @@ Future<void> asyncTests() async {
// Test rejection Promise chaining.
{
Future<JSAny?> f = getRejectedPromise().toDart.then((_) {
final f = getRejectedPromise().toDart.then((_) {
fail('Expected rejected promise to throw.');
return null;
}, onError: (e) {
@ -398,20 +469,24 @@ Future<void> asyncTests() async {
await f;
}
// Test resolving promise with null and undefined.
Future<void> testResolveWithNullOrUndefined(bool resolveWithNull) async {
Future<JSAny?> f =
resolvePromiseWithNullOrUndefined(resolveWithNull).toDart;
expect(((await f) as JSAny?), null);
// Test resolving generic promise with null and undefined.
Future<void> testResolveWithNullOrUndefined<T extends JSAny?>(
bool resolveWithNull) async {
final f = resolvePromiseWithNullOrUndefined<T>(resolveWithNull).toDart;
expect(await f, null);
}
await testResolveWithNullOrUndefined(true);
await testResolveWithNullOrUndefined(false);
await testResolveWithNullOrUndefined<JSNumber?>(true);
await testResolveWithNullOrUndefined<JSNumber?>(false);
// Test rejecting promise with null and undefined should trigger an exception.
Future<void> testRejectionWithNullOrUndefined(bool rejectWithNull) async {
// Test rejecting generic promise with null and undefined should trigger an
// exception.
Future<void> testRejectionWithNullOrUndefined<T extends JSAny?>(
bool rejectWithNull) async {
try {
await rejectPromiseWithNullOrUndefined(rejectWithNull).toDart;
await rejectPromiseWithNullOrUndefined<T>(rejectWithNull).toDart;
fail('Expected rejected promise to throw.');
} catch (e) {
expect(e is NullRejectionException, true);
@ -420,14 +495,53 @@ Future<void> asyncTests() async {
await testRejectionWithNullOrUndefined(true);
await testRejectionWithNullOrUndefined(false);
await testRejectionWithNullOrUndefined<JSNumber?>(true);
await testRejectionWithNullOrUndefined<JSNumber?>(false);
// [Future<JSAny?>] -> [JSPromise].
// [Future] -> [JSPromise].
// Test resolution.
{
final f = Future<JSAny?>(() => 'resolved'.toJS).toJS.toDart;
expect(((await f) as JSString).toDart, 'resolved');
}
// Test resolution with generics.
{
final f = Future<JSString>(() => 'resolved'.toJS).toJS.toDart;
expect((await f).toDart, 'resolved');
}
// Test resolution with incorrect types. Depending on the backend and the type
// test, the promise may throw when its resolved or when the resolved value is
// internalized.
// TODO(54214): These type errors are not caught in the JS compilers
// correctly.
// {
// try {
// final f =
// (Future<JSString>(() => 'resolved'.toJS).toJS as JSPromise<JSBoolean>)
// .toDart;
// final jsBool = await f;
// // TODO(54179): This should be a `jsBool.toDart` call, but currently we
// // try to coerce all extern refs into primitive types in this conversion
// // method. Change this once that bug is fixed.
// if (!jsBool.typeofEquals('boolean')) throw TypeError();
// fail('Expected resolution or use of type to throw.');
// } catch (e) {
// expect(e is TypeError, true);
// }
// // Incorrect nullability.
// try {
// final f =
// (Future<JSString?>(() => null).toJS as JSPromise<JSString>).toDart;
// await f;
// fail('Expected incorrect nullability to throw.');
// } catch (e) {
// expect(e is TypeError, true);
// }
// }
// Test rejection.
{
try {
@ -456,7 +570,9 @@ Future<void> asyncTests() async {
// Test rejection.
{
try {
await Future<void>(() => throw Exception()).toJS.toDart as Future<void>;
final f =
Future<void>(() => throw Exception()).toJS.toDart as Future<void>;
await f;
fail('Expected future to throw.');
} catch (e) {
expect(e is JSObject, true);