Add external extension methods.
CFE transformation to add a function body for external
extension methods. Routes to `js_util.callMethod` with the
arguments in a ListLiteral to then be further optimized to
the `callMethodUnchecked` version if possible.
Change-Id: Iccdda7b8c16c19b37e20b7cbceb8c32498e64d3b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213721
Commit-Queue: Riley Porter <rileyporter@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
index dacab35..cb04456 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
@@ -88,19 +88,17 @@
@override
visitProcedure(Procedure node) {
_staticTypeContext.enterMember(node);
- // Getters, setters, and fields declared as `static` will be skipped,
- // because they do not have a node.kind of ProcedureKind.Method.
- if (node.isExternal &&
- node.isExtensionMember &&
- node.kind == ProcedureKind.Method) {
+ if (node.isExternal && node.isExtensionMember && !_isDeclaredStatic(node)) {
var transformedBody;
if (api.getExtensionMemberKind(node) == ProcedureKind.Getter) {
transformedBody = _getExternalGetterBody(node);
} else if (api.getExtensionMemberKind(node) == ProcedureKind.Setter) {
transformedBody = _getExternalSetterBody(node);
+ } else if (api.getExtensionMemberKind(node) == ProcedureKind.Method) {
+ transformedBody = _getExternalMethodBody(node);
}
- // TODO(rileyporter): Add transformation for external extension methods,
- // static members, and any operators we decide to support.
+ // TODO(rileyporter): Add transformation for static members and any
+ // operators we decide to support.
if (transformedBody != null) {
node.function.body = transformedBody;
node.isExternal = false;
@@ -149,13 +147,50 @@
_lowerSetProperty(setPropertyInvocation), function.returnType));
}
+ /// Returns a new function body for the given [node] external method.
+ ///
+ /// The new function body will call the optimized version of
+ /// `js_util.callMethod` for the given external method.
+ ReturnStatement _getExternalMethodBody(Procedure node) {
+ var function = node.function;
+ var callMethodInvocation = StaticInvocation(
+ _callMethodTarget,
+ Arguments([
+ VariableGet(function.positionalParameters.first),
+ StringLiteral(_getMemberName(node)),
+ ListLiteral(function.positionalParameters
+ .sublist(1)
+ .map((argument) => VariableGet(argument))
+ .toList())
+ ]))
+ ..fileOffset = node.fileOffset;
+ return ReturnStatement(AsExpression(
+ _lowerCallMethod(callMethodInvocation), function.returnType));
+ }
+
/// Returns the member name, either from the `@JS` annotation if non-empty,
/// or parsed from CFE generated node name.
String _getMemberName(Procedure node) {
var jsAnnotationName = getJSName(node);
- return jsAnnotationName.isNotEmpty
- ? jsAnnotationName
- : node.name.text.substring(node.name.text.indexOf('#') + 1);
+ if (jsAnnotationName.isNotEmpty) {
+ return jsAnnotationName;
+ }
+ // TODO(rileyporter): Switch to using the ExtensionMemberDescriptor data.
+ var nodeName = node.name.text;
+ if (nodeName.contains('#')) {
+ return nodeName.substring(nodeName.indexOf('#') + 1);
+ } else {
+ return nodeName.substring(nodeName.indexOf('|') + 1);
+ }
+ }
+
+ /// Returns whether the given extension [node] is declared as `static`.
+ ///
+ /// All extension members have `isStatic` true, but the members declared as
+ /// static will not have a synthesized `this` variable.
+ bool _isDeclaredStatic(Procedure node) {
+ return node.function.positionalParameters.isEmpty ||
+ node.function.positionalParameters.first.name != '#this';
}
/// Replaces js_util method calls with optimization when possible.
diff --git a/tests/lib/js/external_extension_members_test.dart b/tests/lib/js/external_extension_members_test.dart
index 6c9caa2..823861c 100644
--- a/tests/lib/js/external_extension_members_test.dart
+++ b/tests/lib/js/external_extension_members_test.dart
@@ -33,6 +33,14 @@
external set setter(_);
@JS('setterAnnotation')
external set annotatedSetter(_);
+
+ external num getField();
+ @JS('toString')
+ external String extToString();
+ external dynamic getFirstEl(list);
+ external num sumFn(a, b);
+ @JS('sumFn')
+ external num otherSumFn(a, b);
}
@JS('module.Bar')
@@ -56,6 +64,22 @@
this.getterAnnotation = a;
}
+ Foo.prototype.toString = function() {
+ return "Foo: " + this.field;
+ }
+
+ Foo.prototype.getField = function() {
+ return this.field;
+ }
+
+ Foo.prototype.getFirstEl = function(list) {
+ return list[0];
+ }
+
+ Foo.prototype.sumFn = function(a, b) {
+ return a + b;
+ }
+
var module = {Bar: Foo};
""");
@@ -94,6 +118,16 @@
expect(js_util.getProperty(foo, 'setterAnnotation'), equals('whale'));
});
+ test('methods', () {
+ var foo = Foo(42);
+
+ expect(foo.getField(), equals(42));
+ expect(foo.extToString(), equals('Foo: 42'));
+ expect(foo.getFirstEl([1, 2, 3]), equals(1));
+ expect(foo.sumFn(2, 3), equals(5));
+ expect(foo.otherSumFn(10, 5), equals(15));
+ });
+
test('module class', () {
var bar = Bar(5);
expect(js_util.getProperty(bar, 'fieldAnnotation'), equals(5));
diff --git a/tests/lib_2/js/external_extension_members_test.dart b/tests/lib_2/js/external_extension_members_test.dart
index a2c08a1..b1b0cfe 100644
--- a/tests/lib_2/js/external_extension_members_test.dart
+++ b/tests/lib_2/js/external_extension_members_test.dart
@@ -30,6 +30,14 @@
external set setter(_);
@JS('setterAnnotation')
external set annotatedSetter(_);
+
+ external num getField();
+ @JS('toString')
+ external String extToString();
+ external dynamic getFirstEl(list);
+ external num sumFn(a, b);
+ @JS('sumFn')
+ external num otherSumFn(a, b);
}
@JS('module.Bar')
@@ -55,6 +63,22 @@
this.getterAnnotation = a;
}
+ Foo.prototype.toString = function() {
+ return "Foo: " + this.field;
+ }
+
+ Foo.prototype.getField = function() {
+ return this.field;
+ }
+
+ Foo.prototype.getFirstEl = function(list) {
+ return list[0];
+ }
+
+ Foo.prototype.sumFn = function(a, b) {
+ return a + b;
+ }
+
var module = {Bar: Foo};
""");
@@ -76,6 +100,16 @@
expect(js_util.getProperty(foo, 'setterAnnotation'), equals('whale'));
});
+ test('methods', () {
+ var foo = Foo(42);
+
+ expect(foo.getField(), equals(42));
+ expect(foo.extToString(), equals('Foo: 42'));
+ expect(foo.getFirstEl([1, 2, 3]), equals(1));
+ expect(foo.sumFn(2, 3), equals(5));
+ expect(foo.otherSumFn(10, 5), equals(15));
+ });
+
test('module class', () {
var bar = Bar(5);
expect(js_util.getProperty(bar, 'fieldAnnotation'), equals(5));