[dart2wasm] Implement promiseToFuture.

Change-Id: I265441c5c78933f8bdace53f956d22677442d7fe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/257803
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Joshua Litt 2022-09-07 17:56:17 +00:00 committed by Commit Bot
parent 70e8dc6ee4
commit eb540194ad
5 changed files with 97 additions and 6 deletions

View file

@ -58,6 +58,7 @@ class JsUtilWasmOptimizer extends Transformer {
final Field _pragmaOptions;
final Member _globalThisMember;
int _functionTrampolineN = 1;
late Library _library;
final CoreTypes _coreTypes;
final StatefulStaticTypeContext _staticTypeContext;
@ -93,6 +94,7 @@ class JsUtilWasmOptimizer extends Transformer {
@override
Library visitLibrary(Library lib) {
_library = lib;
_staticTypeContext.enterLibrary(lib);
lib.transformChildren(this);
_staticTypeContext.leaveLibrary(lib);
@ -227,7 +229,6 @@ class JsUtilWasmOptimizer extends Transformer {
/// matches.
String _createFunctionTrampoline(Procedure node, FunctionType function) {
int fileOffset = node.fileOffset;
Library library = node.enclosingLibrary;
// Create arguments for each positional parameter in the function. These
// arguments will be converted in JS to Dart objects. The generated wrapper
@ -256,11 +257,12 @@ class JsUtilWasmOptimizer extends Transformer {
// a native JS value before being returned to JS.
DartType nullableWasmAnyRefType =
_wasmAnyRefClass.getThisType(_coreTypes, Nullability.nullable);
final String libraryName = _library.name ?? 'Unnamed';
final functionTrampolineName =
'|_functionTrampoline${_functionTrampolineN++}';
'|_functionTrampoline${_functionTrampolineN++}For$libraryName';
final functionTrampolineImportName = '\$$functionTrampolineName';
final functionTrampoline = Procedure(
Name(functionTrampolineName, library),
Name(functionTrampolineName, _library),
ProcedureKind.Method,
FunctionNode(
ReturnStatement(StaticInvocation(
@ -285,7 +287,7 @@ class JsUtilWasmOptimizer extends Transformer {
_pragmaOptions.fieldReference:
StringConstant(functionTrampolineImportName)
})));
library.addProcedure(functionTrampoline);
_library.addProcedure(functionTrampoline);
return functionTrampolineImportName;
}

View file

@ -357,6 +357,9 @@ var dart2wasm = {
areEqualInJS: function(l, r) {
return l === r;
},
promiseThen: function(promise, successFunc, failureFunc) {
promise.then(successFunc, failureFunc);
},
};
function instantiate(filename, imports) {

View file

@ -307,6 +307,10 @@ external String stringify(WasmAnyRef? object);
@pragma("wasm:import", "dart2wasm.objectKeys")
external WasmAnyRef objectKeysRaw(WasmAnyRef? o);
@pragma("wasm:import", "dart2wasm.promiseThen")
external void promiseThen(
WasmAnyRef promise, WasmAnyRef successFunc, WasmAnyRef failureFunc);
// Currently, `allowInterop` returns a Function type. This is unfortunate for
// Dart2wasm because it means arbitrary Dart functions can flow to JS util
// calls. Our only solutions is to cache every function called with

View file

@ -4,8 +4,10 @@
library dart.js_util;
import "dart:_js_annotations" as js;
import "dart:_internal";
import "dart:_js_helper";
import "dart:async" show Completer, FutureOr;
import "dart:collection";
import "dart:typed_data";
import "dart:wasm";
@ -141,8 +143,32 @@ bool lessThan<T>(Object? first, Object? second) => throw 'unimplemented';
@patch
bool lessThanOrEqual<T>(Object? first, Object? second) => throw 'unimplemented';
typedef _PromiseSuccessFunc = void Function(Object? value);
typedef _PromiseFailureFunc = void Function(Object? error);
@patch
Future<T> promiseToFuture<T>(Object jsPromise) => throw 'unimplemented';
Future<T> promiseToFuture<T>(Object jsPromise) {
Completer<T> completer = Completer<T>();
final success = js.allowInterop<_PromiseSuccessFunc>((r) {
return completer.complete(r as FutureOr<T>?);
});
final error = js.allowInterop<_PromiseFailureFunc>((e) {
// Note that `completeError` expects a non-nullable error regardless of
// whether null-safety is enabled, so a `NullRejectionException` is always
// provided if the error is `null` or `undefined`.
// TODO(joshualitt): At this point `undefined` has been replaced with `null`
// so we cannot tell them apart. In the future we should reify `undefined`
// in Dart.
if (e == null) {
return completer.completeError(NullRejectionException._(false));
}
return completer.completeError(e);
});
promiseThen(jsifyRaw(jsPromise)!, jsifyRaw(success)!, jsifyRaw(error)!);
return completer.future;
}
@patch
Object? objectGetPrototypeOf(Object? object) => throw 'unimplemented';

View file

@ -5,6 +5,7 @@
import 'dart:js_util';
import 'dart:typed_data';
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:js/js.dart';
@ -252,10 +253,65 @@ void deepConversionsTest() {
callMethod(gt, 'invoke', <Object?>[dartify(getProperty(gt, 'f'))]));
}
void main() {
Future<void> promiseToFutureTest() async {
Object gt = globalThis;
eval(r'''
globalThis.rejectedPromise = new Promise((resolve, reject) => reject('rejected'));
globalThis.resolvedPromise = new Promise(resolve => resolve('resolved'));
globalThis.getResolvedPromise = function() {
return resolvedPromise;
}
//globalThis.nullRejectedPromise = Promise.reject(null);
''');
// Test resolved
{
Future f = promiseToFuture(getProperty(gt, 'resolvedPromise'));
Expect.equals('resolved', await f);
}
// Test rejected
{
String result = await asyncExpectThrows<String>(
promiseToFuture(getProperty(gt, 'rejectedPromise')));
Expect.equals('rejected', result);
}
// Test return resolved
{
Future f = promiseToFuture(callMethod(gt, 'getResolvedPromise', []));
Expect.equals('resolved', await f);
}
// Test promise chaining
{
bool didThen = false;
Future f = promiseToFuture(callMethod(gt, 'getResolvedPromise', []));
f.then((resolved) {
Expect.equals(resolved, 'resolved');
didThen = true;
});
await f;
Expect.isTrue(didThen);
}
// Test rejecting promise with null should trigger an exception.
// TODO(joshualitt): Fails with an illegal cast.
// {
// Future f = promiseToFuture(getProperty(gt, 'nullRejectedPromise'));
// f.then((_) { Expect.fail("Expect promise to reject"); }).catchError((e) {
// print('A');
// Expect.isTrue(e is NullRejectionException);
// });
// await f;
// }
}
void main() async {
createObjectTest();
equalTest();
evalAndConstructTest();
dartObjectRoundTripTest();
deepConversionsTest();
await promiseToFutureTest();
}