Skip to content

Commit 6f85292

Browse files
srujzsCommit Queue
authored andcommitted
[dart2wasm] Support NullRejectionException.isUndefined
Previously, the implementation worked around boxing limitations by always setting this field to false. However, because we control both the callback and the JS code that calls the rejection function, we can pass more information to the callback to let it know whether the error was undefined. Change-Id: Ic19f24ec20d321859eb8184d5c87744f7679cf0c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/439141 Commit-Queue: Srujan Gaddam <[email protected]> Reviewed-by: Ömer Ağacan <[email protected]>
1 parent ee2e81e commit 6f85292

File tree

5 files changed

+57
-34
lines changed

5 files changed

+57
-34
lines changed

sdk/lib/_internal/wasm/lib/js_helper.dart

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,22 @@ String typeof(WasmExternRef? object) =>
266266
String stringify(WasmExternRef? object) =>
267267
JSStringImpl.fromRefUnchecked(JS<WasmExternRef?>("o => String(o)", object));
268268

269-
void promiseThen(
269+
/// `Promise.then` call where [failureFunc] can be a JS function that expects
270+
/// two arguments, the first being the error, and the second being whether the
271+
/// error was undefined.
272+
///
273+
/// The second argument is needed as dart2wasm implicitly converts all JS
274+
/// `undefined`s to Dart `null` when boxing JS values.
275+
void promiseThenWithIsUndefined(
270276
WasmExternRef? promise,
271277
WasmExternRef? successFunc,
272278
WasmExternRef? failureFunc,
273-
) => JS<void>("(p, s, f) => p.then(s, f)", promise, successFunc, failureFunc);
279+
) => JS<void>(
280+
"(p, s, f) => p.then(s, (e) => f(e, e === undefined))",
281+
promise,
282+
successFunc,
283+
failureFunc,
284+
);
274285

275286
// Currently, `allowInterop` returns a Function type. This is unfortunate for
276287
// Dart2wasm because it means arbitrary Dart functions can flow to JS util

sdk/lib/_internal/wasm/lib/js_interop_patch.dart

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -207,21 +207,20 @@ extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
207207
completer.completeError(e);
208208
}
209209
}.toJS;
210-
final error = (JSAny? e) {
211-
// TODO(joshualitt): Investigate reifying `JSNull` and `JSUndefined` on
212-
// all backends and if it is feasible, or feasible for some limited use
213-
// cases, then we should pass [e] directly to `completeError`.
214-
// TODO(joshualitt): Use helpers to avoid conflating `null` and `JSNull` /
215-
// `JSUndefined`.
210+
final error = (JSAny? e, bool isUndefined) {
211+
// `e` is null when the original error is either JS `null` or JS
212+
// `undefined`.
216213
if (e == null) {
217-
// Note that we pass false as a default. It's not currently possible to
218-
// be able to differentiate between null and undefined.
219-
completer.completeError(js_util.NullRejectionException(false));
214+
completer.completeError(js_util.NullRejectionException(isUndefined));
220215
return;
221216
}
222217
completer.completeError(e);
223218
}.toJS;
224-
promiseThen(toExternRef, success.toExternRef, error.toExternRef);
219+
js_helper.promiseThenWithIsUndefined(
220+
toExternRef,
221+
success.toExternRef,
222+
error.toExternRef,
223+
);
225224
return completer.future;
226225
}
227226
}

sdk/lib/_internal/wasm/lib/js_util_patch.dart

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,21 +171,21 @@ Future<T> promiseToFuture<T>(Object jsPromise) {
171171
final r = dartifyRaw(jsValue.toExternRef);
172172
return completer.complete(r as FutureOr<T>?);
173173
}).toJS;
174-
final error = ((JSAny? jsError) {
175-
// Note that `completeError` expects a non-nullable error regardless of
176-
// whether null-safety is enabled, so a `NullRejectionException` is always
177-
// provided if the error is `null` or `undefined`.
178-
// TODO(joshualitt): At this point `undefined` has been replaced with `null`
179-
// so we cannot tell them apart. In the future we should reify `undefined`
180-
// in Dart.
174+
final error = (JSAny? jsError, bool isUndefined) {
175+
// `jsError` is null when the original error is either JS `null` or JS
176+
// `undefined`.
181177
final e = dartifyRaw(jsError.toExternRef);
182178
if (e == null) {
183-
return completer.completeError(NullRejectionException(false));
179+
completer.completeError(NullRejectionException(isUndefined));
180+
return;
184181
}
185-
return completer.completeError(e);
186-
}).toJS;
187-
188-
promiseThen(jsifyRaw(jsPromise), success.toExternRef, error.toExternRef);
182+
completer.completeError(e);
183+
}.toJS;
184+
promiseThenWithIsUndefined(
185+
jsifyRaw(jsPromise),
186+
success.toExternRef,
187+
error.toExternRef,
188+
);
189189
return completer.future;
190190
}
191191

tests/lib/js/static_interop_test/js_types_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ Future<void> asyncTests() async {
683683
Expect.fail('Expected rejected promise to throw.');
684684
} catch (e) {
685685
Expect.isTrue(e is NullRejectionException);
686+
Expect.equals(rejectWithNull, !(e as NullRejectionException).isUndefined);
686687
}
687688
}
688689

tests/web/wasm/js_util_test.dart

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,8 @@ Future<void> promiseToFutureTest() async {
355355
globalThis.getResolvedPromise = function() {
356356
return resolvedPromise;
357357
}
358-
//globalThis.nullRejectedPromise = Promise.reject(null);
358+
globalThis.nullRejectedPromise = new Promise((resolve, reject) => reject(null));
359+
globalThis.undefinedRejectedPromise = new Promise((resolve, reject) => reject(undefined));
359360
''');
360361

361362
// Test resolved
@@ -391,15 +392,26 @@ Future<void> promiseToFutureTest() async {
391392
}
392393

393394
// Test rejecting promise with null should trigger an exception.
394-
// TODO(joshualitt): Fails with an illegal cast.
395-
// {
396-
// Future f = promiseToFuture(getProperty(gt, 'nullRejectedPromise'));
397-
// f.then((_) { Expect.fail("Expect promise to reject"); }).catchError((e) {
398-
// print('A');
399-
// Expect.isTrue(e is NullRejectionException);
400-
// });
401-
// await f;
402-
// }
395+
{
396+
try {
397+
await promiseToFuture(getProperty(gt, 'nullRejectedPromise'));
398+
Expect.fail("Expect promise to reject");
399+
} catch (e) {
400+
Expect.isTrue(e is NullRejectionException);
401+
Expect.isFalse((e as NullRejectionException).isUndefined);
402+
}
403+
}
404+
405+
// Test rejecting promise with undefined should trigger an exception.
406+
{
407+
try {
408+
await promiseToFuture(getProperty(gt, 'undefinedRejectedPromise'));
409+
Expect.fail("Expect promise to reject");
410+
} catch (e) {
411+
Expect.isTrue(e is NullRejectionException);
412+
Expect.isTrue((e as NullRejectionException).isUndefined);
413+
}
414+
}
403415
}
404416

405417
@JS('Symbol')

0 commit comments

Comments
 (0)