[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  {