[js] Specialize optional arguments at call sites.

Change-Id: I59b10242a5d85907c21fc99eaf1eff15b39d7191
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/285701
Commit-Queue: Joshua Litt <joshualitt@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Joshua Litt 2023-03-06 19:59:32 +00:00 committed by Commit Queue
parent 47cab3d803
commit c36862af90
12 changed files with 368 additions and 74 deletions

View file

@ -17,6 +17,20 @@ import '../js_interop.dart'
hasStaticInteropAnnotation,
hasTrustTypesAnnotation;
enum _MethodSpecializationType {
constructor,
method,
}
class _MethodCallSiteSpecialization {
final bool shouldTrustType;
final Expression? maybeReceiver;
final _MethodSpecializationType type;
_MethodCallSiteSpecialization(
this.shouldTrustType, this.maybeReceiver, this.type);
}
/// Replaces js_util methods with inline calls to foreign_helper JS which
/// emits the code as a JavaScript code fragment.
class JsUtilOptimizer extends Transformer {
@ -32,6 +46,8 @@ class JsUtilOptimizer extends Transformer {
final InterfaceType _objectType;
final Procedure _setPropertyTarget;
final Procedure _setPropertyUncheckedTarget;
final Map<Procedure, _MethodCallSiteSpecialization> _proceduresToSpecialize =
{};
/// Dynamic members in js_util that interop allowed.
static final Iterable<String> _allowedInteropJsUtilMembers = <String>[
@ -116,9 +132,17 @@ class JsUtilOptimizer extends Transformer {
return node;
}
@override
visitProcedure(Procedure node) {
_staticTypeContext.enterMember(node);
// TODO(joshualitt): Here and in `js_runtime_generator.dart`, there is
// complexity related to the fact that we lower procedures, and also
// specialize invocations. We need to do the latter to cleanly support
// optional parameters, and we currently do the former to support tearoffs.
// However, the tearoffs will not be consistent with the specialized
// invocations, and this may be confusing. We should consider disallowing
// tearoffs of external procedures, which will ensure consistency.
bool tryTransformProcedure(Procedure node) {
if (_proceduresToSpecialize.containsKey(node)) {
return true;
}
ReturnStatement? transformedBody;
if (node.isExternal) {
if (_inlineExtensionIndex.isInstanceInteropMember(node)) {
@ -163,7 +187,15 @@ class JsUtilOptimizer extends Transformer {
node.function.body = transformedBody;
transformedBody.parent = node.function;
node.isExternal = false;
} else {
return true;
}
return false;
}
@override
visitProcedure(Procedure node) {
_staticTypeContext.enterMember(node);
if (!tryTransformProcedure(node)) {
node.transformChildren(this);
}
_staticTypeContext.leaveMember(node);
@ -346,32 +378,46 @@ class JsUtilOptimizer extends Transformer {
/// The new function body will call the optimized version of
/// `js_util.callMethod` for the given external method. If [shouldTrustType]
/// is true, we call a variant that does not check the return type. If
/// [receiver] is non-null, we use that instead of the first positional
/// [maybeReceiver] is non-null, we use that instead of the first positional
/// parameter as the receiver for `js_util.callMethod`.
ReturnStatement _getExternalMethodBody(Procedure node, bool shouldTrustType,
[Expression? receiver]) {
var function = node.function;
[Expression? maybeReceiver]) {
if (_inlineExtensionIndex.isJSInteropMember(node)) {
_proceduresToSpecialize[node] = _MethodCallSiteSpecialization(
shouldTrustType, maybeReceiver, _MethodSpecializationType.method);
}
return ReturnStatement(_getExternalMethodInvocation(
node,
shouldTrustType,
node.function.positionalParameters
.map<Expression>((v) => VariableGet(v))
.toList(),
maybeReceiver));
}
StaticInvocation _getExternalMethodInvocation(
Procedure node, bool shouldTrustType, List<Expression> arguments,
[Expression? maybeReceiver]) {
final function = node.function;
Procedure target =
shouldTrustType ? _callMethodTrustTypeTarget : _callMethodTarget;
var positionalParameters = function.positionalParameters;
final receiver = maybeReceiver ?? arguments.first;
if (_inlineExtensionIndex.isInstanceInteropMember(node)) {
// Ignore `this` for inline and extension members.
positionalParameters = positionalParameters.sublist(1);
arguments = arguments.sublist(1);
}
var callMethodInvocation = StaticInvocation(
target,
Arguments([
receiver ?? VariableGet(function.positionalParameters.first),
receiver,
StringLiteral(_getMemberJSName(node)),
ListLiteral(positionalParameters
.map<Expression>((argument) => VariableGet(argument))
.toList())
ListLiteral(arguments),
], types: [
function.returnType
]))
..fileOffset = node.fileOffset;
return ReturnStatement(_lowerCallMethod(callMethodInvocation,
shouldTrustType: shouldTrustType));
return _lowerCallMethod(callMethodInvocation,
shouldTrustType: shouldTrustType);
}
/// Returns a new function body for the given [node] external operator.
@ -399,20 +445,28 @@ class JsUtilOptimizer extends Transformer {
/// of the provided external factory.
ReturnStatement _getExternalConstructorBody(
Procedure node, Expression constructor) {
if (_inlineExtensionIndex.isJSInteropMember(node)) {
_proceduresToSpecialize[node] = _MethodCallSiteSpecialization(
false, constructor, _MethodSpecializationType.constructor);
}
return ReturnStatement(_getExternalConstructorInvocation(
node,
constructor,
node.function.positionalParameters
.map<Expression>((argument) => VariableGet(argument))
.toList()));
}
StaticInvocation _getExternalConstructorInvocation(
Procedure node, Expression constructor, List<Expression> parameters) {
var function = node.function;
assert(function.namedParameters.isEmpty);
var callConstructorInvocation = StaticInvocation(
_callConstructorTarget,
Arguments([
constructor,
ListLiteral(function.positionalParameters
.map<Expression>((argument) => VariableGet(argument))
.toList())
], types: [
function.returnType
]))
Arguments([constructor, ListLiteral(parameters)],
types: [function.returnType]))
..fileOffset = node.fileOffset;
return ReturnStatement(_lowerCallConstructor(callConstructorInvocation));
return _lowerCallConstructor(callConstructorInvocation);
}
/// Returns the underlying JS name.
@ -453,13 +507,40 @@ class JsUtilOptimizer extends Transformer {
/// 0-4 arguments and all arguments are guaranteed to be interop allowed.
@override
visitStaticInvocation(StaticInvocation node) {
if (node.target == _setPropertyTarget) {
final target = node.target;
if (target == _setPropertyTarget) {
node = _lowerSetProperty(node);
} else if (node.target == _callMethodTarget) {
} else if (target == _callMethodTarget) {
// Never trust types on explicit `js_util` calls.
node = _lowerCallMethod(node, shouldTrustType: false);
} else if (node.target == _callConstructorTarget) {
} else if (target == _callConstructorTarget) {
node = _lowerCallConstructor(node);
} else if (target.isExternal) {
tryTransformProcedure(target);
}
// Make sure to call [tryTransformProcedure] before specializing, just in
// case we haven't visited the [Procedure] yet.
if (_proceduresToSpecialize.containsKey(target)) {
final function = target.function;
final positional = node.arguments.positional;
if (positional.length < function.positionalParameters.length) {
final specialization = _proceduresToSpecialize[target]!;
switch (specialization.type) {
case _MethodSpecializationType.method:
node = _getExternalMethodInvocation(
target,
specialization.shouldTrustType,
positional,
specialization.maybeReceiver)
..fileOffset = node.fileOffset;
break;
case _MethodSpecializationType.constructor:
node = _getExternalConstructorInvocation(
target, specialization.maybeReceiver!, positional)
..fileOffset = node.fileOffset;
}
}
}
node.transformChildren(this);
return node;
@ -640,6 +721,7 @@ class JsUtilOptimizer extends Transformer {
/// Lazily-initialized indexes for extension members and inline class members.
class InlineExtensionIndex {
late Map<Reference, Annotatable> _extensionAnnotatableIndex;
Map<Reference, ExtensionMemberDescriptor>? _extensionMemberIndex;
late Map<Reference, InlineClass> _inlineClassIndex;
Map<Reference, InlineClassMemberDescriptor>? _inlineMemberIndex;
@ -660,6 +742,7 @@ class InlineExtensionIndex {
if (_extensionMemberIndex != null) return;
_extensionMemberIndex = {};
_shouldTrustType = {};
_extensionAnnotatableIndex = {};
for (var extension in _library.extensions) {
for (var descriptor in extension.members) {
var reference = descriptor.member;
@ -678,11 +761,17 @@ class InlineExtensionIndex {
if (cls == null) continue;
if (hasJSInteropAnnotation(cls) || hasNativeAnnotation(cls)) {
_extensionMemberIndex![reference] = descriptor;
_extensionAnnotatableIndex[reference] = cls;
}
}
}
}
Annotatable? getExtensionAnnotatable(Reference reference) {
_createExtensionIndexes();
return _extensionAnnotatableIndex[reference];
}
ExtensionMemberDescriptor? getExtensionDescriptor(Reference reference) {
_createExtensionIndexes();
return _extensionMemberIndex![reference];
@ -791,4 +880,29 @@ class InlineExtensionIndex {
}
return name;
}
bool isJSInteropMember(Procedure node) {
if (hasInternalJSInteropAnnotation(node) ||
hasInternalJSInteropAnnotation(node.enclosingLibrary) ||
(node.enclosingClass != null &&
hasInternalJSInteropAnnotation(node.enclosingClass!))) {
return true;
}
if (node.isExtensionMember) {
final annotatable = getExtensionAnnotatable(node.reference);
if (annotatable != null) {
return hasInternalJSInteropAnnotation(annotatable);
}
}
if (node.isInlineClassMember) {
final cls = getInlineClass(node.reference);
if (cls != null) {
return hasInternalJSInteropAnnotation(cls);
}
}
return false;
}
}

View file

@ -141,6 +141,7 @@ class _JSLowerer extends Transformer {
int _methodN = 1;
late Library _library;
late String _libraryJSString;
final Map<Procedure, Map<int, Procedure>> _overloadedProcedures = {};
final CoreTypes _coreTypes;
late InlineExtensionIndex _inlineExtensionIndex;
@ -212,16 +213,28 @@ class _JSLowerer extends Transformer {
@override
Expression visitStaticInvocation(StaticInvocation node) {
node = super.visitStaticInvocation(node) as StaticInvocation;
if (node.target == _allowInteropTarget) {
Expression argument = node.arguments.positional.single;
List<Expression> positional = node.arguments.positional;
Procedure target = node.target;
if (target == _allowInteropTarget) {
Expression argument = positional.single;
DartType functionType = argument.getStaticType(_staticTypeContext);
return _allowInterop(node.target, functionType as FunctionType, argument);
} else if (node.target == _functionToJSTarget) {
Expression argument = node.arguments.positional.single;
} else if (target == _functionToJSTarget) {
Expression argument = positional.single;
DartType functionType = argument.getStaticType(_staticTypeContext);
return _functionToJS(node.target, functionType as FunctionType, argument);
return _functionToJS(target, functionType as FunctionType, argument);
} else if (node.target == _inlineJSTarget) {
return _expandInlineJS(node.target, node);
} else if (target.isExternal) {
tryTransformProcedure(target);
}
if (_overloadedProcedures.containsKey(target)) {
final overloads = _overloadedProcedures[target]!;
int positionalLength = positional.length;
if (overloads.containsKey(positionalLength)) {
return StaticInvocation(overloads[positionalLength]!, node.arguments);
}
}
return node;
}
@ -259,10 +272,11 @@ class _JSLowerer extends Transformer {
}
}
@override
Procedure visitProcedure(Procedure node) {
_staticTypeContext.enterMember(node);
Statement? transformedBody;
bool tryTransformProcedure(Procedure node) {
if (_overloadedProcedures.containsKey(node)) {
return true;
}
if (node.isExternal) {
_MethodType? type;
String jsString = '';
@ -272,7 +286,14 @@ class _JSLowerer extends Transformer {
jsString = _getTopLevelJSString(cls, cls.name);
if (node.isFactory) {
if (hasAnonymousAnnotation(cls)) {
type = _MethodType.jsObjectLiteralConstructor;
// TODO(joshualitt): These should really be lowered at the
// invocation level.
_specializeJSObjectLiteral(_MethodLoweringConfig(
node,
_MethodType.jsObjectLiteralConstructor,
jsString,
_inlineExtensionIndex));
return true;
} else {
type = _MethodType.constructor;
}
@ -332,14 +353,18 @@ class _JSLowerer extends Transformer {
type = _getTypeForNonExtensionMember(node);
}
if (type != null) {
transformedBody = _specializeJSMethod(
_specializeProcedureWithOptionalParameters(
_MethodLoweringConfig(node, type, jsString, _inlineExtensionIndex));
return true;
}
}
if (transformedBody != null) {
node.function.body = transformedBody..parent = node.function;
node.isExternal = false;
} else {
return false;
}
@override
Procedure visitProcedure(Procedure node) {
_staticTypeContext.enterMember(node);
if (!tryTransformProcedure(node)) {
// Under very restricted circumstances, we will make a procedure external
// and clear it's body. See the description on [_expandInlineJS] for more
// details.
@ -657,12 +682,20 @@ class _JSLowerer extends Transformer {
functionType:
target.function.computeFunctionType(Nullability.nonNullable));
// Specializes a JS method for a given [_MethodLoweringConfig] and returns an
// invocation of the specialized method.
Statement _specializeJSMethod(_MethodLoweringConfig config) {
void _specializeJSObjectLiteral(_MethodLoweringConfig config) {
Procedure procedure = config.procedure;
Statement? transformedBody =
_specializeProcedure(config, config.parameters);
procedure.function.body = transformedBody..parent = procedure.function;
procedure.isExternal = false;
}
/// Creates a Dart procedure that calls out to a specialized JS method for the
/// given [config] and returns the created procedure.
Statement _specializeProcedure(_MethodLoweringConfig config,
List<VariableDeclaration> originalParameters) {
// Initialize variable declarations.
List<String> jsParameterStrings = [];
List<VariableDeclaration> originalParameters = config.parameters;
List<VariableDeclaration> dartPositionalParameters = [];
for (int j = 0; j < originalParameters.length; j++) {
String parameterString = 'x$j';
@ -744,6 +777,62 @@ class _JSLowerer extends Transformer {
}
}
/// Specializes a JS method for a given [config] while handling optional
/// parameters. We will generate one procedure for every optional argument,
/// and make all of the arguments to that procedure required. For the time
/// being to support tearoffs we simply replace the body of the original
/// procedure, but leave the optional arguments intact. This unfortunately
/// results in inconsistent behavior between the tearoff and the original
/// functions.
/// TODO(joshualitt): Decide if we should disallow tearoffs of external
/// functions, and if so we can clean this up.
void _specializeProcedureWithOptionalParameters(
_MethodLoweringConfig config) {
// First handle optional arguments by creating a specialized procedure for
// each optional, and make all of the arguments required. These will be used
// when we visit static invocations to specialize calls.
Procedure procedure = config.procedure;
FunctionNode function = procedure.function;
int requiredParameterCount = function.requiredParameterCount;
List<VariableDeclaration> positionalParameters =
function.positionalParameters;
Map<int, Procedure> overloadMap = {};
for (int i = requiredParameterCount; i < positionalParameters.length; i++) {
List<VariableDeclaration> newParameters = positionalParameters
.sublist(0, i)
.map((v) => VariableDeclaration(v.name, flags: v.flags, type: v.type))
.toList();
Statement body = _specializeProcedure(config, newParameters);
String procedureName = '|${generateMethodName()}';
Procedure specializedProcedure = Procedure(
Name(procedureName, _library),
procedure.kind,
FunctionNode(body,
requiredParameterCount: i,
positionalParameters: newParameters,
returnType: function.returnType),
fileUri: procedure.fileUri)
..isStatic = procedure.isStatic
..fileOffset = procedure.fileOffset;
if (procedure.parent is Class) {
procedure.enclosingClass!.addProcedure(specializedProcedure);
} else {
procedure.enclosingLibrary.addProcedure(specializedProcedure);
}
overloadMap[i] = specializedProcedure;
}
// Finally, create a specialized body to replace the original external
// procedure. This will be used for tearoffs and in cases where all
// arguments are specified at the call site.
Statement transformedBody =
_specializeProcedure(config, positionalParameters);
function.body = transformedBody..parent = function;
procedure.isExternal = false;
overloadMap[positionalParameters.length] = procedure;
_overloadedProcedures[procedure] = overloadMap;
}
Procedure? _tryGetEnclosingProcedure(TreeNode? node) {
while (node is! Procedure) {
node = node?.parent;

View file

@ -40,7 +40,7 @@ class ExternalStatic {
}
extension on ExternalStatic {
external String get initialValue;
external String? get initialValue;
}
// Top-level fields.
@ -120,7 +120,7 @@ void testClassStaticMembers() {
// Methods and tear-offs.
expect(ExternalStatic.method(), 'method');
expect((ExternalStatic.method)(), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
'optionalmethod');
expect(ExternalStatic.renamedMethod(), 'method');
@ -148,7 +148,7 @@ void testTopLevelMembers() {
// Methods and tear-offs.
expect(method(), 'method');
expect((method)(), 'method');
expect(differentArgsMethod('method'), 'method');
expect(differentArgsMethod('method'), 'methodundefined');
expect((differentArgsMethod)('optional', 'method'), 'optionalmethod');
expect(renamedMethod(), 'method');
expect((renamedMethod)(), 'method');
@ -157,12 +157,11 @@ void testTopLevelMembers() {
void testFactories() {
// Non-object literal factories and their tear-offs.
var initialized = 'initialized';
var uninitialized = 'uninitialized';
var externalStatic = ExternalStatic(initialized);
expect(externalStatic.initialValue, initialized);
externalStatic = ExternalStatic.named();
expect(externalStatic.initialValue, uninitialized);
expect(externalStatic.initialValue, null);
externalStatic = (ExternalStatic.new)(initialized);
expect(externalStatic.initialValue, initialized);

View file

@ -41,7 +41,7 @@ class ExternalStatic {
}
extension on ExternalStatic {
external String get initialValue;
external String? get initialValue;
}
void main() {
@ -135,7 +135,7 @@ void testClassStaticMembers() {
// Methods and tearoffs.
expect(ExternalStatic.method(), 'method');
expect((ExternalStatic.method)(), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
'optionalmethod');
expect(ExternalStatic.renamedMethod(), 'method');
@ -165,7 +165,7 @@ void testTopLevelMembers() {
// Methods and tear-offs.
expect(method(), 'method');
expect((method)(), 'method');
expect(differentArgsMethod('method'), 'method');
expect(differentArgsMethod('method'), 'methodundefined');
expect((differentArgsMethod)('optional', 'method'), 'optionalmethod');
expect(namespacedMethod(), 'namespacedMethod');
expect((namespacedMethod)(), 'namespacedMethod');
@ -174,12 +174,11 @@ void testTopLevelMembers() {
void testFactories() {
// Non-object literal factories and their tear-offs.
var initialized = 'initialized';
var uninitialized = 'uninitialized';
var externalStatic = ExternalStatic(initialized);
expect(externalStatic.initialValue, initialized);
externalStatic = ExternalStatic.named();
expect(externalStatic.initialValue, uninitialized);
expect(externalStatic.initialValue, null);
externalStatic = (ExternalStatic.new)(initialized);
expect(externalStatic.initialValue, initialized);

View file

@ -75,7 +75,7 @@ void main() {
// Methods and tear-offs.
expect(extension.method(), 'method');
expect((extension.method)(), 'method');
expect(extension.differentArgsMethod('method'), 'method');
expect(extension.differentArgsMethod('method'), 'methodundefined');
expect((extension.differentArgsMethod)('optional', 'method'),
'optionalmethod');
expect(extension.renamedMethod(), 'method');

View file

@ -80,7 +80,7 @@ void main() {
// tear-off tests.
expect(external.method(), 'method');
// expect((external.method)(), 'method');
expect(external.differentArgsMethod('method'), 'method');
expect(external.differentArgsMethod('method'), 'methodundefined');
// expect((external.differentArgsMethod)('optional', 'method'),
// 'optionalmethod');
expect(external.renamedMethod(), 'method');

View file

@ -93,7 +93,7 @@ void main() {
// Methods and tear-offs.
expect(ExternalStatic.method(), 'method');
expect((ExternalStatic.method)(), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
'optionalmethod');
expect(ExternalStatic.renamedMethod(), 'method');

View file

@ -101,7 +101,7 @@ void main() {
// Methods and tear-offs.
expect(ExternalStatic.method(), 'method');
expect((ExternalStatic.method)(), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
'optionalmethod');
expect(ExternalStatic.renamedMethod(), 'method');

View file

@ -0,0 +1,95 @@
// 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.
import 'dart:js_interop';
import 'package:expect/minitest.dart';
import 'package:js/js.dart' hide JS;
@JS()
external void eval(String code);
@JS()
@staticInterop
class SimpleObject {
external factory SimpleObject();
external factory SimpleObject.twoOptional([JSNumber n1, JSNumber n2]);
external factory SimpleObject.oneOptional(JSNumber n1, [JSNumber n2]);
external static JSNumber twoOptionalStatic([JSNumber n1, JSNumber n2]);
external static JSNumber oneOptionalStatic(JSNumber n1, [JSNumber n2]);
}
extension SimpleObjectExtension on SimpleObject {
external JSNumber get initialArguments;
external JSNumber twoOptional([JSNumber n1, JSNumber n2]);
external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
}
@JS()
external JSNumber twoOptional([JSNumber n1, JSNumber n2]);
@JS()
external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);
void main() {
eval('''
globalThis.twoOptional = function(n1, n2) {
return arguments.length;
}
globalThis.oneOptional = function(n1, n2) {
return arguments.length;
}
globalThis.SimpleObject = function(i1, i2) {
this.twoOptional = function(n1, n2) {
return arguments.length;
}
this.oneOptional = function(n1, n2) {
return arguments.length;
}
this.initialArguments = arguments.length;
return this;
}
globalThis.SimpleObject.twoOptionalStatic = function(n1, n2) {
return arguments.length;
}
globalThis.SimpleObject.oneOptionalStatic = function(n1, n2) {
return arguments.length;
}
''');
// Test top level methods.
expect(0, twoOptional().toDart);
expect(1, twoOptional(4.0.toJS).toDart);
expect(2, twoOptional(4.0.toJS, 5.0.toJS).toDart);
expect(1, oneOptional(4.0.toJS).toDart);
expect(2, oneOptional(4.0.toJS, 5.0.toJS).toDart);
// Test factories.
expect(0, SimpleObject.twoOptional().initialArguments.toDart);
expect(1, SimpleObject.twoOptional(4.0.toJS).initialArguments.toDart);
expect(
2, SimpleObject.twoOptional(4.0.toJS, 5.0.toJS).initialArguments.toDart);
expect(1, SimpleObject.oneOptional(4.0.toJS).initialArguments.toDart);
expect(
2, SimpleObject.oneOptional(4.0.toJS, 5.0.toJS).initialArguments.toDart);
// Test static methods.
expect(0, SimpleObject.twoOptionalStatic().toDart);
expect(1, SimpleObject.twoOptionalStatic(4.0.toJS).toDart);
expect(2, SimpleObject.twoOptionalStatic(4.0.toJS, 5.0.toJS).toDart);
expect(1, SimpleObject.oneOptionalStatic(4.0.toJS).toDart);
expect(2, SimpleObject.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDart);
// Test extension methods.
final s = SimpleObject();
expect(0, s.twoOptional().toDart);
expect(1, s.twoOptional(4.0.toJS).toDart);
expect(2, s.twoOptional(4.0.toJS, 5.0.toJS).toDart);
expect(1, s.oneOptional(4.0.toJS).toDart);
expect(2, s.oneOptional(4.0.toJS, 5.0.toJS).toDart);
}

View file

@ -40,7 +40,7 @@ class ExternalStatic {
}
extension on ExternalStatic {
external String get initialValue;
external String? get initialValue;
}
// Top-level fields.
@ -120,7 +120,7 @@ void testClassStaticMembers() {
// Methods and tear-offs.
expect(ExternalStatic.method(), 'method');
expect((ExternalStatic.method)(), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
'optionalmethod');
expect(ExternalStatic.renamedMethod(), 'method');
@ -148,7 +148,7 @@ void testTopLevelMembers() {
// Methods and tear-offs.
expect(method(), 'method');
expect((method)(), 'method');
expect(differentArgsMethod('method'), 'method');
expect(differentArgsMethod('method'), 'methodundefined');
expect((differentArgsMethod)('optional', 'method'), 'optionalmethod');
expect(renamedMethod(), 'method');
expect((renamedMethod)(), 'method');
@ -157,12 +157,11 @@ void testTopLevelMembers() {
void testFactories() {
// Non-object literal factories and their tear-offs.
var initialized = 'initialized';
var uninitialized = 'uninitialized';
var externalStatic = ExternalStatic(initialized);
expect(externalStatic.initialValue, initialized);
externalStatic = ExternalStatic.named();
expect(externalStatic.initialValue, uninitialized);
expect(externalStatic.initialValue, null);
externalStatic = (ExternalStatic.new)(initialized);
expect(externalStatic.initialValue, initialized);

View file

@ -41,7 +41,7 @@ class ExternalStatic {
}
extension on ExternalStatic {
external String get initialValue;
external String? get initialValue;
}
void main() {
@ -135,7 +135,7 @@ void testClassStaticMembers() {
// Methods and tearoffs.
expect(ExternalStatic.method(), 'method');
expect((ExternalStatic.method)(), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'method');
expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
'optionalmethod');
expect(ExternalStatic.renamedMethod(), 'method');
@ -165,7 +165,7 @@ void testTopLevelMembers() {
// Methods and tear-offs.
expect(method(), 'method');
expect((method)(), 'method');
expect(differentArgsMethod('method'), 'method');
expect(differentArgsMethod('method'), 'methodundefined');
expect((differentArgsMethod)('optional', 'method'), 'optionalmethod');
expect(namespacedMethod(), 'namespacedMethod');
expect((namespacedMethod)(), 'namespacedMethod');
@ -174,12 +174,11 @@ void testTopLevelMembers() {
void testFactories() {
// Non-object literal factories and their tear-offs.
var initialized = 'initialized';
var uninitialized = 'uninitialized';
var externalStatic = ExternalStatic(initialized);
expect(externalStatic.initialValue, initialized);
externalStatic = ExternalStatic.named();
expect(externalStatic.initialValue, uninitialized);
expect(externalStatic.initialValue, null);
externalStatic = (ExternalStatic.new)(initialized);
expect(externalStatic.initialValue, initialized);

View file

@ -78,10 +78,10 @@ void createClassTest() {
return (a ?? 'foo') + (b ?? 'bar');
}
this.doSum1Or2NonNull = function(a, b) {
return a + b;
return (a ?? 'foo') + (b ?? 'bar');
}
this.doSumUpTo2NonNull = function(a, b) {
return a + b;
return (a ?? 'foo') + (b ?? 'bar');
}
this.doIntSum1Or2 = function(a, b) {
return a + (b ?? 2);
@ -90,10 +90,10 @@ void createClassTest() {
return (a ?? 1) + (b ?? 2);
}
this.doIntSum1Or2NonNull = function(a, b) {
return a + b;
return a + (b ?? 2);
}
this.doIntSumUpTo2NonNull = function(a, b) {
return a + b;
return (a ?? 1) + (b ?? 2);
}
this.nameInJSMethod = function(a, b) {
return a + b;