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>
This commit is contained in:
Riley Porter 2021-09-22 19:52:10 +00:00 committed by commit-bot@chromium.org
parent 1379c867a4
commit a0d9d35b80
3 changed files with 113 additions and 10 deletions

View file

@ -88,19 +88,17 @@ class JsUtilOptimizer extends Transformer {
@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 @@ class JsUtilOptimizer extends Transformer {
_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.

View file

@ -33,6 +33,14 @@ extension FooExt on Foo {
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 @@ void main() {
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 @@ void main() {
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));

View file

@ -30,6 +30,14 @@ extension FooExt on Foo {
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 @@ void main() {
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 @@ void main() {
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));