mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 12:48:10 +00:00
[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:
parent
47cab3d803
commit
c36862af90
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
95
tests/lib/js/static_interop_test/js_default_test.dart
Normal file
95
tests/lib/js/static_interop_test/js_default_test.dart
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue