mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
[dart2wasm] Implement optional parameters for JS interop callbacks.
Also added tests of optional parameters for static interop functions. Change-Id: Id23237b96d0de5a4a4b948b3f23fd1bfe40b218e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/259101 Reviewed-by: Srujan Gaddam <srujzs@google.com> Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
parent
9e9768c89d
commit
8429f01593
|
@ -240,7 +240,8 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
/// [String] function name representing the name of the wrapping function.
|
||||
/// TODO(joshualitt): Share callback trampolines if the [FunctionType]
|
||||
/// matches.
|
||||
String _createFunctionTrampoline(Procedure node, FunctionType function) {
|
||||
String _createFunctionTrampoline(
|
||||
Procedure node, FunctionType function, Expression argument) {
|
||||
int fileOffset = node.fileOffset;
|
||||
|
||||
// Create arguments for each positional parameter in the function. These
|
||||
|
@ -257,11 +258,50 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
List<Expression> callbackArguments = [];
|
||||
DartType nullableObjectType =
|
||||
_objectClass.getThisType(_coreTypes, Nullability.nullable);
|
||||
for (DartType type in function.positionalParameters) {
|
||||
for (int i = 0; i < function.positionalParameters.length; i++) {
|
||||
DartType type = function.positionalParameters[i];
|
||||
Expression? defaultExpression;
|
||||
bool hasDefault = i >= function.requiredParameterCount;
|
||||
if (hasDefault) {
|
||||
// We can only generate default values if we have a statically typed
|
||||
// function argument.
|
||||
Expression? initializer;
|
||||
if (argument is ConstantExpression) {
|
||||
Procedure callbackTarget = (argument.constant as TearOffConstant)
|
||||
.targetReference
|
||||
.asProcedure;
|
||||
initializer =
|
||||
callbackTarget.function.positionalParameters[i].initializer;
|
||||
} else if (argument is FunctionExpression) {
|
||||
initializer = argument.function.positionalParameters[i].initializer;
|
||||
} else {
|
||||
throw 'Cannot pass default arguments.';
|
||||
}
|
||||
|
||||
// The initializer for a default argument must be a
|
||||
// [ConstantExpression].
|
||||
ConstantExpression init = initializer as ConstantExpression;
|
||||
defaultExpression = ConstantExpression(init.constant, init.type);
|
||||
}
|
||||
VariableDeclaration variable =
|
||||
VariableDeclaration('x${parameterId++}', type: nullableObjectType);
|
||||
positionalParameters.add(variable);
|
||||
callbackArguments.add(AsExpression(VariableGet(variable), type));
|
||||
Expression body;
|
||||
if (hasDefault) {
|
||||
body = ConditionalExpression(
|
||||
StaticInvocation(
|
||||
_coreTypes.identicalProcedure,
|
||||
Arguments([
|
||||
VariableGet(variable),
|
||||
ConstantExpression(NullConstant())
|
||||
])),
|
||||
defaultExpression ?? ConstantExpression(NullConstant()),
|
||||
VariableGet(variable),
|
||||
nullableObjectType);
|
||||
} else {
|
||||
body = VariableGet(variable);
|
||||
}
|
||||
callbackArguments.add(AsExpression(body, type));
|
||||
}
|
||||
|
||||
// Create a new procedure for the callback trampoline. This procedure will
|
||||
|
@ -308,11 +348,17 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
/// [_createFunctionTrampoline] followed by `_wrapDartFunction`.
|
||||
StaticInvocation _allowInterop(
|
||||
Procedure node, FunctionType type, Expression argument) {
|
||||
String functionTrampolineName = _createFunctionTrampoline(node, type);
|
||||
String functionTrampolineName =
|
||||
_createFunctionTrampoline(node, type, argument);
|
||||
return StaticInvocation(
|
||||
_wrapDartFunctionTarget,
|
||||
Arguments([argument, StringLiteral(functionTrampolineName)],
|
||||
types: [type]));
|
||||
Arguments([
|
||||
argument,
|
||||
StringLiteral(functionTrampolineName),
|
||||
ConstantExpression(IntConstant(type.positionalParameters.length))
|
||||
], types: [
|
||||
type
|
||||
]));
|
||||
}
|
||||
|
||||
StaticGet get _globalThis => StaticGet(_globalThisMember);
|
||||
|
|
|
@ -150,8 +150,13 @@ var dart2wasm = {
|
|||
},
|
||||
stringFromDartString: stringFromDartString,
|
||||
stringToDartString: stringToDartString,
|
||||
wrapDartFunction: function(dartFunction, exportFunctionName) {
|
||||
wrapDartFunction: function(dartFunction, exportFunctionName, argCount) {
|
||||
var wrapped = function (...args) {
|
||||
// Pad `undefined` for optional arguments that aren't passed so that
|
||||
// the trampoline can replace these values with defaults.
|
||||
while (args.length < argCount) {
|
||||
args.push(undefined);
|
||||
}
|
||||
return dartInstance.exports[`${exportFunctionName}`](
|
||||
dartFunction, ...args.map(dartInstance.exports.$dartifyRaw));
|
||||
}
|
||||
|
|
|
@ -510,15 +510,18 @@ List<Object?> toDartList(WasmExternRef? ref) => List<Object?>.generate(
|
|||
objectLength(ref).round(), (int n) => dartifyRaw(objectReadIndex(ref, n)));
|
||||
|
||||
@pragma("wasm:import", "dart2wasm.wrapDartFunction")
|
||||
external WasmExternRef? _wrapDartFunctionRaw(
|
||||
WasmExternRef? dartFunction, WasmExternRef? trampolineName);
|
||||
external WasmExternRef? _wrapDartFunctionRaw(WasmExternRef? dartFunction,
|
||||
WasmExternRef? trampolineName, WasmExternRef? argCount);
|
||||
|
||||
F _wrapDartFunction<F extends Function>(F f, String trampolineName) {
|
||||
F _wrapDartFunction<F extends Function>(
|
||||
F f, String trampolineName, int argCount) {
|
||||
if (functionToJSWrapper.containsKey(f)) {
|
||||
return f;
|
||||
}
|
||||
JSValue wrappedFunction = JSValue(_wrapDartFunctionRaw(
|
||||
f.toJS().toExternRef(), trampolineName.toJS().toExternRef())!);
|
||||
f.toJS().toExternRef(),
|
||||
trampolineName.toJS().toExternRef(),
|
||||
argCount.toDouble().toJS().toExternRef())!);
|
||||
functionToJSWrapper[f] = wrappedFunction;
|
||||
return f;
|
||||
}
|
||||
|
|
|
@ -11,51 +11,148 @@ import 'package:js/js.dart';
|
|||
@JS()
|
||||
external void eval(String code);
|
||||
|
||||
typedef SumStringCallback = String Function(String a, String b);
|
||||
typedef SumTwoPositionalFun = String Function(String a, String b);
|
||||
typedef SumOnePositionalAndOneOptionalFun = String Function(String a,
|
||||
[String? b]);
|
||||
typedef SumTwoOptionalFun = String Function([String? a, String? b]);
|
||||
typedef SumOnePositionalAndOneOptionalNonNullFun = String Function(String a,
|
||||
[String b]);
|
||||
typedef SumTwoOptionalNonNullFun = String Function([String a, String b]);
|
||||
|
||||
@JS()
|
||||
@staticInterop
|
||||
class DartFromJSCallbackHelper {
|
||||
external factory DartFromJSCallbackHelper.factory(SumStringCallback summer);
|
||||
external factory DartFromJSCallbackHelper.factory(
|
||||
SumTwoPositionalFun sumTwoPositional,
|
||||
SumOnePositionalAndOneOptionalFun sumOnePositionalOneOptional,
|
||||
SumTwoOptionalFun sumTwoOptional,
|
||||
SumOnePositionalAndOneOptionalNonNullFun
|
||||
sumOnePositionalAndOneOptionalNonNull,
|
||||
SumTwoOptionalNonNullFun sumTwoOptionalNonNull);
|
||||
}
|
||||
|
||||
extension DartFromJSCallbackHelperMethods on DartFromJSCallbackHelper {
|
||||
external String doSum1();
|
||||
external String doSum2(String a, String b);
|
||||
external String doSum3(Object summer);
|
||||
|
||||
external String doSumOnePositionalAndOneOptionalA(String a);
|
||||
external String doSumOnePositionalAndOneOptionalB(String a, String b);
|
||||
external String doSumTwoOptionalA();
|
||||
external String doSumTwoOptionalB(String a);
|
||||
external String doSumTwoOptionalC(String a, String b);
|
||||
|
||||
external String doSumOnePositionalAndOneOptionalANonNull(String a);
|
||||
external String doSumOnePositionalAndOneOptionalBNonNull(String a, String b);
|
||||
external String doSumTwoOptionalANonNull();
|
||||
external String doSumTwoOptionalBNonNull(String a);
|
||||
external String doSumTwoOptionalCNonNull(String a, String b);
|
||||
}
|
||||
|
||||
String sumString(String a, String b) {
|
||||
String sumTwoPositional(String a, String b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
String sumOnePositionalAndOneOptional(String a, [String? b]) {
|
||||
return a + (b ?? 'bar');
|
||||
}
|
||||
|
||||
String sumTwoOptional([String? a, String? b]) {
|
||||
return (a ?? 'foo') + (b ?? 'bar');
|
||||
}
|
||||
|
||||
String sumOnePositionalAndOneOptionalNonNull(String a, [String b = 'bar']) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
String sumTwoOptionalNonNull([String a = 'foo', String b = 'bar']) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
void staticInteropCallbackTest() {
|
||||
eval(r'''
|
||||
globalThis.DartFromJSCallbackHelper = function(summer) {
|
||||
globalThis.DartFromJSCallbackHelper = function(
|
||||
sumTwoPositional, sumOnePositionalOneOptional, sumTwoOptional,
|
||||
sumOnePositionalAndOneOptionalNonNull, sumTwoOptionalNonNull) {
|
||||
this.a = 'hello ';
|
||||
this.b = 'world!';
|
||||
this.sum = null;
|
||||
this.summer = summer;
|
||||
this.sumTwoPositional = sumTwoPositional;
|
||||
this.sumOnePositionalOneOptional = sumOnePositionalOneOptional;
|
||||
this.sumTwoOptional = sumTwoOptional;
|
||||
this.sumOnePositionalAndOneOptionalNonNull = sumOnePositionalAndOneOptionalNonNull;
|
||||
this.sumTwoOptionalNonNull = sumTwoOptionalNonNull;
|
||||
this.doSum1 = () => {
|
||||
return this.summer(this.a, this.b);
|
||||
return this.sumTwoPositional(this.a, this.b);
|
||||
}
|
||||
this.doSum2 = (a, b) => {
|
||||
return this.summer(a, b);
|
||||
return this.sumTwoPositional(a, b);
|
||||
}
|
||||
this.doSum3 = (summer) => {
|
||||
return summer(this.a, this.b);
|
||||
}
|
||||
this.doSumOnePositionalAndOneOptionalA = (a) => {
|
||||
return sumOnePositionalOneOptional(a);
|
||||
}
|
||||
this.doSumOnePositionalAndOneOptionalB = (a, b) => {
|
||||
return sumOnePositionalOneOptional(a, b);
|
||||
}
|
||||
this.doSumTwoOptionalA = () => {
|
||||
return sumTwoOptional();
|
||||
}
|
||||
this.doSumTwoOptionalB = (a) => {
|
||||
return sumTwoOptional(a);
|
||||
}
|
||||
this.doSumTwoOptionalC = (a, b) => {
|
||||
return sumTwoOptional(a, b);
|
||||
}
|
||||
this.doSumOnePositionalAndOneOptionalANonNull = (a) => {
|
||||
return sumOnePositionalAndOneOptionalNonNull(a);
|
||||
}
|
||||
this.doSumOnePositionalAndOneOptionalBNonNull = (a, b) => {
|
||||
return sumOnePositionalAndOneOptionalNonNull(a, b);
|
||||
}
|
||||
this.doSumTwoOptionalANonNull = () => {
|
||||
return sumTwoOptionalNonNull();
|
||||
}
|
||||
this.doSumTwoOptionalBNonNull = (a) => {
|
||||
return sumTwoOptionalNonNull(a);
|
||||
}
|
||||
this.doSumTwoOptionalCNonNull = (a, b) => {
|
||||
return sumTwoOptionalNonNull(a, b);
|
||||
}
|
||||
|
||||
}
|
||||
''');
|
||||
|
||||
final dartFromJSCallbackHelper = DartFromJSCallbackHelper.factory(
|
||||
allowInterop<SumStringCallback>(sumString));
|
||||
Expect.equals('hello world!', dartFromJSCallbackHelper.doSum1());
|
||||
Expect.equals('foobar', dartFromJSCallbackHelper.doSum2('foo', 'bar'));
|
||||
final helper = DartFromJSCallbackHelper.factory(
|
||||
allowInterop<SumTwoPositionalFun>(sumTwoPositional),
|
||||
allowInterop<SumOnePositionalAndOneOptionalFun>(
|
||||
sumOnePositionalAndOneOptional),
|
||||
allowInterop<SumTwoOptionalFun>(sumTwoOptional),
|
||||
allowInterop<SumOnePositionalAndOneOptionalNonNullFun>(
|
||||
sumOnePositionalAndOneOptionalNonNull),
|
||||
allowInterop<SumTwoOptionalNonNullFun>(sumTwoOptionalNonNull));
|
||||
|
||||
Expect.equals('hello world!', helper.doSum1());
|
||||
Expect.equals('foobar', helper.doSum2('foo', 'bar'));
|
||||
Expect.equals('hello world!',
|
||||
helper.doSum3(allowInterop<SumTwoPositionalFun>((a, b) => a + b)));
|
||||
|
||||
Expect.equals('foobar', helper.doSumOnePositionalAndOneOptionalA('foo'));
|
||||
Expect.equals(
|
||||
'hello world!',
|
||||
dartFromJSCallbackHelper
|
||||
.doSum3(allowInterop<SumStringCallback>((a, b) => a + b)));
|
||||
'foobar', helper.doSumOnePositionalAndOneOptionalB('foo', 'bar'));
|
||||
Expect.equals('foobar', helper.doSumTwoOptionalA());
|
||||
Expect.equals('foobar', helper.doSumTwoOptionalB('foo'));
|
||||
Expect.equals('foobar', helper.doSumTwoOptionalC('foo', 'bar'));
|
||||
|
||||
Expect.equals(
|
||||
'foobar', helper.doSumOnePositionalAndOneOptionalANonNull('foo'));
|
||||
Expect.equals(
|
||||
'foobar', helper.doSumOnePositionalAndOneOptionalBNonNull('foo', 'bar'));
|
||||
Expect.equals('foobar', helper.doSumTwoOptionalANonNull());
|
||||
Expect.equals('foobar', helper.doSumTwoOptionalBNonNull('foo'));
|
||||
Expect.equals('foobar', helper.doSumTwoOptionalCNonNull('foo', 'bar'));
|
||||
}
|
||||
|
||||
void allowInteropCallbackTest() {
|
||||
|
@ -66,17 +163,109 @@ void allowInteropCallbackTest() {
|
|||
globalThis.doSum2 = function(a, b) {
|
||||
return globalThis.summer(a, b);
|
||||
}
|
||||
globalThis.doSumOnePositionalAndOneOptionalA = function(a) {
|
||||
return summer(a);
|
||||
}
|
||||
globalThis.doSumOnePositionalAndOneOptionalB = function(a, b) {
|
||||
return summer(a, b);
|
||||
}
|
||||
globalThis.doSumTwoOptionalA = function() {
|
||||
return summer();
|
||||
}
|
||||
globalThis.doSumTwoOptionalB = function(a) {
|
||||
return summer(a);
|
||||
}
|
||||
globalThis.doSumTwoOptionalC = function(a, b) {
|
||||
return summer(a, b);
|
||||
}
|
||||
globalThis.doSumOnePositionalAndOneOptionalANonNull = function(a) {
|
||||
return summer(a);
|
||||
}
|
||||
globalThis.doSumOnePositionalAndOneOptionalBNonNull = function(a, b) {
|
||||
return summer(a, b);
|
||||
}
|
||||
globalThis.doSumTwoOptionalANonNull = function() {
|
||||
return summer();
|
||||
}
|
||||
globalThis.doSumTwoOptionalBNonNull = function(a) {
|
||||
return summer(a);
|
||||
}
|
||||
globalThis.doSumTwoOptionalCNonNull = function(a, b) {
|
||||
return summer(a, b);
|
||||
}
|
||||
''');
|
||||
|
||||
final interopCallback = allowInterop<SumStringCallback>((a, b) => a + b);
|
||||
Expect.equals(
|
||||
'foobar', callMethod(globalThis, 'doSum1', [interopCallback]).toString());
|
||||
setProperty(globalThis, 'summer', interopCallback);
|
||||
Expect.equals(
|
||||
'foobar', callMethod(globalThis, 'doSum2', ['foo', 'bar']).toString());
|
||||
final roundTripCallback = getProperty(globalThis, 'summer');
|
||||
Expect.equals('foobar',
|
||||
(dartify(roundTripCallback) as SumStringCallback)('foo', 'bar'));
|
||||
// General
|
||||
{
|
||||
final interopCallback = allowInterop<SumTwoPositionalFun>((a, b) => a + b);
|
||||
Expect.equals('foobar',
|
||||
callMethod(globalThis, 'doSum1', [interopCallback]).toString());
|
||||
setProperty(globalThis, 'summer', interopCallback);
|
||||
Expect.equals(
|
||||
'foobar', callMethod(globalThis, 'doSum2', ['foo', 'bar']).toString());
|
||||
final roundTripCallback = getProperty(globalThis, 'summer');
|
||||
Expect.equals('foobar',
|
||||
(dartify(roundTripCallback) as SumTwoPositionalFun)('foo', 'bar'));
|
||||
}
|
||||
|
||||
// 1 nullable optional argument
|
||||
{
|
||||
final interopCallback = allowInterop<SumOnePositionalAndOneOptionalFun>(
|
||||
(a, [b]) => a + (b ?? 'bar'));
|
||||
setProperty(globalThis, 'summer', interopCallback);
|
||||
Expect.equals(
|
||||
'foobar',
|
||||
callMethod(globalThis, 'doSumOnePositionalAndOneOptionalA', ['foo'])
|
||||
.toString());
|
||||
Expect.equals(
|
||||
'foobar',
|
||||
callMethod(
|
||||
globalThis, 'doSumOnePositionalAndOneOptionalB', ['foo', 'bar'])
|
||||
.toString());
|
||||
}
|
||||
|
||||
// All nullable optional arguments
|
||||
{
|
||||
final interopCallback = allowInterop<SumTwoOptionalFun>(
|
||||
([a, b]) => (a ?? 'foo') + (b ?? 'bar'));
|
||||
setProperty(globalThis, 'summer', interopCallback);
|
||||
Expect.equals(
|
||||
'foobar', callMethod(globalThis, 'doSumTwoOptionalA', []).toString());
|
||||
Expect.equals('foobar',
|
||||
callMethod(globalThis, 'doSumTwoOptionalB', ['foo']).toString());
|
||||
Expect.equals('foobar',
|
||||
callMethod(globalThis, 'doSumTwoOptionalC', ['foo', 'bar']).toString());
|
||||
}
|
||||
|
||||
// 1 non-nullable optional argument
|
||||
{
|
||||
final interopCallback =
|
||||
allowInterop<SumOnePositionalAndOneOptionalNonNullFun>(
|
||||
(a, [b = 'bar']) => a + b);
|
||||
setProperty(globalThis, 'summer', interopCallback);
|
||||
Expect.equals(
|
||||
'foobar',
|
||||
callMethod(globalThis, 'doSumOnePositionalAndOneOptionalA', ['foo'])
|
||||
.toString());
|
||||
Expect.equals(
|
||||
'foobar',
|
||||
callMethod(
|
||||
globalThis, 'doSumOnePositionalAndOneOptionalB', ['foo', 'bar'])
|
||||
.toString());
|
||||
}
|
||||
|
||||
// All non-nullable optional arguments
|
||||
{
|
||||
final interopCallback = allowInterop<SumTwoOptionalNonNullFun>(
|
||||
([a = 'foo', b = 'bar']) => a + b);
|
||||
setProperty(globalThis, 'summer', interopCallback);
|
||||
Expect.equals(
|
||||
'foobar', callMethod(globalThis, 'doSumTwoOptionalA', []).toString());
|
||||
Expect.equals('foobar',
|
||||
callMethod(globalThis, 'doSumTwoOptionalB', ['foo']).toString());
|
||||
Expect.equals('foobar',
|
||||
callMethod(globalThis, 'doSumTwoOptionalC', ['foo', 'bar']).toString());
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
|
|
@ -24,6 +24,10 @@ extension StaticJSClassMethods on StaticJSClass {
|
|||
external int? get nullableInt;
|
||||
external int nonNullableIntReturnMethod();
|
||||
external int? nullableIntReturnMethod(bool returnNull);
|
||||
external String doSum1Or2(String a, [String? b]);
|
||||
external String doSumUpTo2([String? a, String? b]);
|
||||
external String doSum1Or2NonNull(String a, [String b = 'bar']);
|
||||
external String doSumUpTo2NonNull([String a = 'foo', String b = 'bar']);
|
||||
}
|
||||
|
||||
void createClassTest() {
|
||||
|
@ -49,6 +53,18 @@ void createClassTest() {
|
|||
this.nonNullableInt = 60.5;
|
||||
this.nullableInt = 100.5;
|
||||
}
|
||||
this.doSum1Or2 = function(a, b) {
|
||||
return a + (b ?? 'bar');
|
||||
}
|
||||
this.doSumUpTo2 = function(a, b) {
|
||||
return (a ?? 'foo') + (b ?? 'bar');
|
||||
}
|
||||
this.doSum1Or2NonNull = function(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
this.doSumUpTo2NonNull = function(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
''');
|
||||
final foo = StaticJSClass.factory('foo');
|
||||
|
@ -68,6 +84,18 @@ void createClassTest() {
|
|||
foo.doublifyNumbers();
|
||||
Expect.equals(100, foo.nullableInt);
|
||||
Expect.equals(60, foo.nonNullableInt);
|
||||
|
||||
Expect.equals('foobar', foo.doSum1Or2('foo'));
|
||||
Expect.equals('foobar', foo.doSum1Or2('foo', 'bar'));
|
||||
Expect.equals('foobar', foo.doSumUpTo2());
|
||||
Expect.equals('foobar', foo.doSumUpTo2('foo'));
|
||||
Expect.equals('foobar', foo.doSumUpTo2('foo', 'bar'));
|
||||
|
||||
Expect.equals('foobar', foo.doSum1Or2NonNull('foo'));
|
||||
Expect.equals('foobar', foo.doSum1Or2NonNull('foo', 'bar'));
|
||||
Expect.equals('foobar', foo.doSumUpTo2NonNull());
|
||||
Expect.equals('foobar', foo.doSumUpTo2NonNull('foo'));
|
||||
Expect.equals('foobar', foo.doSumUpTo2NonNull('foo', 'bar'));
|
||||
}
|
||||
|
||||
@JS('JSClass.NestedJSClass')
|
||||
|
|
Loading…
Reference in a new issue