[js_interop] Use synthesized getter for static getters
The advantages of this approach:
- Annotations on the original getter are preserved, allowing any dart2js `@pragma` to be honored.
- An optimizing compiler (or user via a pragma) can decide to inline or not inline the getter, giving more control over code size.
Change-Id: I719b41640b75634fe00f6b87f95ebca1801cd159
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/418880
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Stephen Adams <sra@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 b70deea..d3b1c1c 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
@@ -64,6 +64,8 @@
final Map<Member, _InvocationBuilder?> _externalInvocationBuilders = {};
final StatefulStaticTypeContext _staticTypeContext;
+ final bool isDart2JS;
+
/// Dynamic members in js_util that interop allowed.
static const List<String> _allowedInteropJsUtilMembers = [
'callConstructor',
@@ -78,7 +80,7 @@
this._coreTypes,
ClassHierarchy hierarchy,
this._extensionIndex, {
- required bool isDart2JS,
+ required this.isDart2JS,
}) : _callMethodTarget = _coreTypes.index.getTopLevelProcedure(
'dart:js_util',
'callMethod',
@@ -212,6 +214,68 @@
return node;
}
+ @override
+ TreeNode visitProcedure(Procedure node) {
+ if (!isDart2JS) return defaultMember(node);
+
+ if (node.isExternal &&
+ node.isStatic &&
+ !JsInteropChecks.isPatchedMember(node)) {
+ if (node.kind == ProcedureKind.Getter) {
+ final dottedPrefix = _getDottedPrefixForStaticallyResolvableMember(
+ node,
+ );
+ if (dottedPrefix != null) {
+ // Convert the external getter into a Dart getter with a body of the
+ // form
+ //
+ // => getPropertyTrustType<dynamic>(
+ // staticInteropGlobalContext(),
+ // "name"
+ // ) as <type>;
+ //
+ // The type argument is `<dynamic>` so the downcast can be removed
+ // under `-O3`.
+
+ final receiver = _getObjectOffGlobalContext(
+ node,
+ dottedPrefix.isEmpty ? [] : dottedPrefix.split('.'),
+ );
+
+ final shouldTrustType =
+ node.enclosingClass != null &&
+ hasTrustTypesAnnotation(node.enclosingClass!);
+
+ Expression expression = StaticInvocation(
+ _getPropertyTrustTypeTarget,
+ Arguments(
+ [receiver, StringLiteral(_getMemberJSName(node))],
+ types: [DynamicType()],
+ ),
+ )..fileOffset = node.fileOffset;
+
+ if (!shouldTrustType) {
+ expression =
+ AsExpression(expression, node.getterType)
+ ..isTypeError = true
+ ..isForDynamic = true
+ ..fileOffset = node.fileOffset;
+ }
+
+ final statement = ReturnStatement(expression)
+ ..fileOffset = node.fileOffset;
+
+ node.function.body = statement;
+ node.isExternal = false;
+ } else {
+ // Leave the static getter member as `external`.
+ }
+ }
+ }
+
+ return defaultMember(node);
+ }
+
/// Given a static interop procedure [node], return a
/// [_InvocationBuilder] that will create new [StaticInvocation]s that
/// replace calls to [node].
@@ -244,6 +308,16 @@
node.enclosingClass != null &&
hasTrustTypesAnnotation(node.enclosingClass!);
if (_extensionIndex.isGetter(node)) {
+ if (isDart2JS) {
+ // Top-level and static external getters are given a Dart body so
+ // that annotations can remain attached. The call to the getter
+ // remains in place. The dart2js optimizer may do inlining.
+ return null;
+ }
+ // For DDC, the call to the getter is replaced with the lowered code
+ // in this transformer.
+ // TODO(http://dartbug.com/60505): Consider also using a synthesized
+ // getter and leaving the call in place.
return _getExternalGetterInvocationBuilder(
node,
shouldTrustType,
diff --git a/pkg/front_end/testcases/dart2js/extension_types/external.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/extension_types/external.dart.strong.transformed.expect
index 7180bfd..2800201 100644
--- a/pkg/front_end/testcases/dart2js/extension_types/external.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/extension_types/external.dart.strong.transformed.expect
@@ -70,13 +70,16 @@
external static extension-type-member method B|set#setter(lowered final self::B% /* erasure=self::A, declared=! */ #this, self::B% /* erasure=self::A, declared=! */ b) → void;
external static extension-type-member method B|get#property(lowered final self::B% /* erasure=self::A, declared=! */ #this) → self::B% /* erasure=self::A, declared=! */;
external static extension-type-member method B|set#property(lowered final self::B% /* erasure=self::A, declared=! */ #this, self::B% /* erasure=self::A, declared=! */ b) → void;
-external static extension-type-member get B|staticField() → self::A;
+static extension-type-member get B|staticField() → self::A
+ return js_2::_getPropertyTrustType<dynamic>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticField") as{TypeError,ForDynamic} self::A;
external static extension-type-member set B|staticField(synthesized self::A #externalFieldValue) → void;
external static extension-type-member method B|staticMethod() → self::A;
external static extension-type-member method B|staticGenericMethod<T extends self::B% /* erasure=self::A, declared=! */>(self::B|staticGenericMethod::T% t) → self::B|staticGenericMethod::T%;
-external static extension-type-member get B|staticGetter() → self::B% /* erasure=self::A, declared=! */;
+static extension-type-member get B|staticGetter() → self::B% /* erasure=self::A, declared=! */
+ return js_2::_getPropertyTrustType<dynamic>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticGetter") as{TypeError,ForDynamic} self::B% /* erasure=self::A, declared=! */;
external static extension-type-member set B|staticSetter(self::B% /* erasure=self::A, declared=! */ b) → void;
-external static extension-type-member get B|staticProperty() → self::B% /* erasure=self::A, declared=! */;
+static extension-type-member get B|staticProperty() → self::B% /* erasure=self::A, declared=! */
+ return js_2::_getPropertyTrustType<dynamic>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticProperty") as{TypeError,ForDynamic} self::B% /* erasure=self::A, declared=! */;
external static extension-type-member set B|staticProperty(self::B% /* erasure=self::A, declared=! */ b) → void;
static method method(self::A a) → void {
self::B% /* erasure=self::A, declared=! */ b1 = js_2::_callConstructorUnchecked1<self::B% /* erasure=self::A, declared=! */>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), a);
@@ -88,13 +91,13 @@
b1 = js_2::getProperty<self::B% /* erasure=self::A, declared=! */>(b2, "getter");
js_2::_setPropertyUnchecked<self::B% /* erasure=self::A, declared=! */>(b1, "setter", b2);
js_2::_setPropertyUnchecked<self::B% /* erasure=self::A, declared=! */>(b1, "property", js_2::getProperty<self::B% /* erasure=self::A, declared=! */>(b2, "property"));
- a = js_2::getProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticField");
+ a = self::B|staticField;
js_2::_setPropertyUnchecked<self::A>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticField", a);
a = js_2::_callMethodUnchecked0<self::A>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticMethod");
b2 = js_2::_callMethodUnchecked1<self::B% /* erasure=self::A, declared=! */>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticGenericMethod", b2);
- b1 = js_2::getProperty<self::B% /* erasure=self::A, declared=! */>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticGetter");
+ b1 = self::B|staticGetter;
js_2::_setPropertyUnchecked<self::B% /* erasure=self::A, declared=! */>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticSetter", b2);
- js_2::_setPropertyUnchecked<self::B% /* erasure=self::A, declared=! */>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticProperty", js_2::getProperty<self::B% /* erasure=self::A, declared=! */>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticProperty"));
+ js_2::_setPropertyUnchecked<self::B% /* erasure=self::A, declared=! */>(js_2::_getPropertyTrustType<core::Object>(_js2::staticInteropGlobalContext, "B"), "staticProperty", self::B|staticProperty);
}
constants {