mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 03:17:55 +00:00
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:
parent
f5c114ad66
commit
82e8948ddc
|
@ -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.
|
||||
|
|
|
@ -8,5 +8,7 @@ environment:
|
|||
dependencies:
|
||||
_fe_analyzer_shared:
|
||||
path: ../_fe_analyzer_shared
|
||||
front_end:
|
||||
path: ../front_end
|
||||
kernel:
|
||||
path: ../kernel
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
107
tests/lib/js/external_extension_members_test.dart
Normal file
107
tests/lib/js/external_extension_members_test.dart
Normal 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));
|
||||
});
|
||||
}
|
89
tests/lib_2/js/external_extension_members_test.dart
Normal file
89
tests/lib_2/js/external_extension_members_test.dart
Normal 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));
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue