Add external extension fields, getters, and setters.

CFE transformation to add a function body for external fields,
getters, and setters, routing to the relevant js_util
getProperty and setProperty optimized calls.

Change-Id: Ia3d0f05fda50f20d217c0a67e0fd636bb774000b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213600
Commit-Queue: Riley Porter <rileyporter@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Riley Porter 2021-09-21 22:56:18 +00:00 committed by commit-bot@chromium.org
parent f5c114ad66
commit 82e8948ddc
6 changed files with 278 additions and 4 deletions

View file

@ -2,11 +2,14 @@
// 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 'package:front_end/src/api_unstable/dart2js.dart' as api;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/type_environment.dart';
import '../js_interop.dart' show getJSName;
/// Replaces js_util methods with inline calls to foreign_helper JS which
/// emits the code as a JavaScript code fragment.
class JsUtilOptimizer extends Transformer {
@ -82,6 +85,79 @@ class JsUtilOptimizer extends Transformer {
return node;
}
@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) {
var transformedBody;
if (api.getExtensionMemberKind(node) == ProcedureKind.Getter) {
transformedBody = _getExternalGetterBody(node);
} else if (api.getExtensionMemberKind(node) == ProcedureKind.Setter) {
transformedBody = _getExternalSetterBody(node);
}
// TODO(rileyporter): Add transformation for external extension methods,
// static members, and any operators we decide to support.
if (transformedBody != null) {
node.function.body = transformedBody;
node.isExternal = false;
}
} else {
node.transformChildren(this);
}
_staticTypeContext.leaveMember(node);
return node;
}
/// Returns a new function body for the given [node] external getter.
///
/// The new function body will call the optimized version of
/// `js_util.getProperty` for the given external getter.
ReturnStatement _getExternalGetterBody(Procedure node) {
var function = node.function;
assert(function.positionalParameters.length == 1);
var getPropertyInvocation = StaticInvocation(
_getPropertyTarget,
Arguments([
VariableGet(function.positionalParameters.first),
StringLiteral(_getMemberName(node))
]))
..fileOffset = node.fileOffset;
return ReturnStatement(AsExpression(
_lowerGetProperty(getPropertyInvocation), function.returnType));
}
/// Returns a new function body for the given [node] external setter.
///
/// The new function body will call the optimized version of
/// `js_util.setProperty` for the given external setter.
ReturnStatement _getExternalSetterBody(Procedure node) {
var function = node.function;
assert(function.positionalParameters.length == 2);
var setPropertyInvocation = StaticInvocation(
_setPropertyTarget,
Arguments([
VariableGet(function.positionalParameters.first),
StringLiteral(_getMemberName(node)),
VariableGet(function.positionalParameters.last)
]))
..fileOffset = node.fileOffset;
return ReturnStatement(AsExpression(
_lowerSetProperty(setPropertyInvocation), 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);
}
/// Replaces js_util method calls with optimization when possible.
///
/// Lowers `getProperty` for any argument type straight to JS fragment call.

View file

@ -8,5 +8,7 @@ environment:
dependencies:
_fe_analyzer_shared:
path: ../_fe_analyzer_shared
front_end:
path: ../front_end
kernel:
path: ../kernel

View file

@ -144,14 +144,14 @@ class Dart2jsTarget extends Target {
var nativeClasses = JsInteropChecks.getNativeClasses(component);
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
for (var library in libraries) {
// TODO (rileyporter): Merge js_util optimizations with other lowerings
// in the single pass in `transformations/lowering.dart`.
jsUtilOptimizer.visitLibrary(library);
JsInteropChecks(
coreTypes,
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
nativeClasses)
.visitLibrary(library);
// TODO (rileyporter): Merge js_util optimizations with other lowerings
// in the single pass in `transformations/lowering.dart`.
jsUtilOptimizer.visitLibrary(library);
}
lowering.transformLibraries(libraries, coreTypes, hierarchy, options);
logger?.call("Lowering transformations performed");

View file

@ -160,12 +160,12 @@ class DevCompilerTarget extends Target {
var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy);
for (var library in libraries) {
_CovarianceTransformer(library).transform();
jsUtilOptimizer.visitLibrary(library);
JsInteropChecks(
coreTypes,
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
_nativeClasses!)
.visitLibrary(library);
jsUtilOptimizer.visitLibrary(library);
}
}

View file

@ -0,0 +1,107 @@
// Copyright (c) 2021, 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.
// Tests behavior of external extension members, which are routed to js_util
// calls by a CFE transformation.
@JS()
library external_extension_members_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/minitest.dart';
@JS()
external void eval(String code);
@JS()
class Foo {
external Foo(int a);
}
extension FooExt on Foo {
external var field;
external final finalField;
@JS('fieldAnnotation')
external var annotatedField;
external get getter;
@JS('getterAnnotation')
external get annotatedGetter;
external set setter(_);
@JS('setterAnnotation')
external set annotatedSetter(_);
}
@JS('module.Bar')
class Bar {
external Bar(int a);
}
extension BarExt on Bar {
@JS('field')
external var barField;
}
void main() {
eval(r"""
function Foo(a) {
this.field = a;
this.fieldAnnotation = a;
this.finalField = a;
this.getter = a;
this.getterAnnotation = a;
}
var module = {Bar: Foo};
""");
test('fields', () {
var foo = Foo(42);
// field getters
expect(foo.field, equals(42));
expect(foo.finalField, equals(42));
expect(foo.annotatedField, equals(42));
// field setters
foo.field = 'squid';
expect(foo.field, equals('squid'));
foo.annotatedField = 'octopus';
expect(foo.annotatedField, equals('octopus'));
js_util.setProperty(foo, 'fieldAnnotation', 'clownfish');
expect(foo.annotatedField, equals('clownfish'));
});
test('getters', () {
var foo = Foo(42);
expect(foo.getter, equals(42));
expect(foo.annotatedGetter, equals(42));
js_util.setProperty(foo, 'getterAnnotation', 'eel');
expect(foo.annotatedGetter, equals('eel'));
});
test('setters', () {
var foo = Foo(42);
foo.setter = 'starfish';
expect(js_util.getProperty(foo, 'setter'), equals('starfish'));
foo.annotatedSetter = 'whale';
expect(js_util.getProperty(foo, 'setterAnnotation'), equals('whale'));
});
test('module class', () {
var bar = Bar(5);
expect(js_util.getProperty(bar, 'fieldAnnotation'), equals(5));
expect(bar.barField, equals(5));
expect(js_util.getProperty(bar, 'field'), equals(5));
bar.barField = 10;
expect(js_util.getProperty(bar, 'fieldAnnotation'), equals(5));
expect(js_util.getProperty(bar, 'field'), equals(10));
});
}

View file

@ -0,0 +1,89 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
// Tests behavior of external extension members, which are routed to js_util
// calls by a CFE transformation.
@JS()
library external_extension_members_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/minitest.dart';
@JS()
external void eval(String code);
@JS()
class Foo {
external Foo(int a);
}
extension FooExt on Foo {
external get getter;
@JS('getterAnnotation')
external get annotatedGetter;
external set setter(_);
@JS('setterAnnotation')
external set annotatedSetter(_);
}
@JS('module.Bar')
class Bar {
external Bar(int a);
}
extension BarExt on Bar {
@JS('field')
external get barFieldGetter;
@JS('field')
external set barFieldSetter(_);
}
void main() {
eval(r"""
function Foo(a) {
this.field = a;
this.fieldAnnotation = a;
this.finalField = a;
this.getter = a;
this.getterAnnotation = a;
}
var module = {Bar: Foo};
""");
test('getters', () {
var foo = Foo(42);
expect(foo.getter, equals(42));
expect(foo.annotatedGetter, equals(42));
js_util.setProperty(foo, 'getterAnnotation', 'eel');
expect(foo.annotatedGetter, equals('eel'));
});
test('setters', () {
var foo = Foo(42);
foo.setter = 'starfish';
expect(js_util.getProperty(foo, 'setter'), equals('starfish'));
foo.annotatedSetter = 'whale';
expect(js_util.getProperty(foo, 'setterAnnotation'), equals('whale'));
});
test('module class', () {
var bar = Bar(5);
expect(js_util.getProperty(bar, 'fieldAnnotation'), equals(5));
expect(bar.barFieldGetter, equals(5));
expect(js_util.getProperty(bar, 'field'), equals(5));
bar.barFieldSetter = 10;
expect(js_util.getProperty(bar, 'fieldAnnotation'), equals(5));
expect(js_util.getProperty(bar, 'field'), equals(10));
});
}