Support native member annotations through ir constants

Change-Id: I247385c951d3a794afe7e4066b3b1d1ea14a76b0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96641
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/ir/annotations.dart b/pkg/compiler/lib/src/ir/annotations.dart
index 710c741..3222787 100644
--- a/pkg/compiler/lib/src/ir/annotations.dart
+++ b/pkg/compiler/lib/src/ir/annotations.dart
@@ -7,6 +7,8 @@
 
 class IrAnnotationData {
   Map<ir.Class, String> _nativeClassNames = {};
+  Set<ir.Member> _nativeMembers = {};
+  Map<ir.Member, String> _nativeMemberNames = {};
 
   Map<ir.Library, String> _jsInteropLibraryNames = {};
   Map<ir.Class, String> _jsInteropClassNames = {};
@@ -15,18 +17,30 @@
 
   Map<ir.Member, List<PragmaAnnotationData>> _memberPragmaAnnotations = {};
 
+  // Returns the text from the `@Native(<text>)` annotation of [node], if any.
   String getNativeClassName(ir.Class node) => _nativeClassNames[node];
 
+  // Returns `true` if [node] has a native body, as in `method() native;`.
+  bool hasNativeBody(ir.Member node) => _nativeMembers.contains(node);
+
+  // Returns the text from the `@JSName(<text>)` annotation of [node], if any.
+  String getNativeMemberName(ir.Member node) => _nativeMemberNames[node];
+
+  // Returns the text from the `@JS(<text>)` annotation of [node], if any.
   String getJsInteropLibraryName(ir.Library node) =>
       _jsInteropLibraryNames[node];
 
+  // Returns the text from the `@JS(<text>)` annotation of [node], if any.
   String getJsInteropClassName(ir.Class node) => _jsInteropClassNames[node];
 
+  // Returns `true` if [node] is annotated with `@anonymous`.
   bool isAnonymousJsInteropClass(ir.Class node) =>
       _anonymousJsInteropClasses.contains(node);
 
+  // Returns the text from the `@JS(<text>)` annotation of [node], if any.
   String getJsInteropMemberName(ir.Member node) => _jsInteropMemberNames[node];
 
+  // Returns a list of the `@pragma('dart2js:<suffix>')` annotations on [node].
   List<PragmaAnnotationData> getMemberPragmaAnnotationData(ir.Member node) =>
       _memberPragmaAnnotations[node] ?? const [];
 }
@@ -39,10 +53,22 @@
     for (ir.Expression annotation in member.annotations) {
       if (annotation is ir.ConstantExpression) {
         ir.Constant constant = annotation.constant;
+
         String jsName = _getJsInteropName(constant);
         if (jsName != null) {
           data._jsInteropMemberNames[member] = jsName;
         }
+
+        bool isNativeMember = _isNativeMember(constant);
+        if (isNativeMember) {
+          data._nativeMembers.add(member);
+        }
+
+        String nativeName = _getNativeMemberName(constant);
+        if (nativeName != null) {
+          data._nativeMemberNames[member] = nativeName;
+        }
+
         PragmaAnnotationData pragmaAnnotation = _getPragmaAnnotation(constant);
         if (pragmaAnnotation != null) {
           if (pragmaAnnotations == null) {
@@ -99,6 +125,8 @@
 
 String _getNativeClassName(ir.Constant constant) {
   if (constant is ir.InstanceConstant) {
+    // TODO(johnniwinther): Add an IrCommonElements for these queries; i.e.
+    // `commonElements.isNativeAnnotationClass(constant.classNode)`.
     if (constant.classNode.name == 'Native' &&
         constant.classNode.enclosingLibrary.importUri == Uris.dart__js_helper) {
       if (constant.fieldValues.length == 1) {
@@ -116,33 +144,44 @@
   return null;
 }
 
+bool _isNativeMember(ir.Constant constant) {
+  return constant is ir.InstanceConstant &&
+      constant.classNode.name == 'ExternalName' &&
+      constant.classNode.enclosingLibrary.importUri == Uris.dart__internal;
+}
+
+String _getNativeMemberName(ir.Constant constant) {
+  if (constant is ir.InstanceConstant &&
+      constant.classNode.name == 'JSName' &&
+      constant.classNode.enclosingLibrary.importUri == Uris.dart__js_helper) {
+    assert(constant.fieldValues.length == 1);
+    ir.Constant fieldValue = constant.fieldValues.values.single;
+    if (fieldValue is ir.StringConstant) {
+      return fieldValue.value;
+    }
+  }
+  return null;
+}
+
 String _getJsInteropName(ir.Constant constant) {
-  if (constant is ir.InstanceConstant) {
-    if (constant.classNode.name == 'JS' &&
-        constant.classNode.enclosingLibrary.importUri == Uris.package_js) {
-      if (constant.fieldValues.length == 1) {
-        ir.Constant fieldValue = constant.fieldValues.values.single;
-        String name;
-        if (fieldValue is ir.NullConstant) {
-          name = '';
-        } else if (fieldValue is ir.StringConstant) {
-          name = fieldValue.value;
-        }
-        if (name != null) {
-          return name;
-        }
-      }
+  if (constant is ir.InstanceConstant &&
+      constant.classNode.name == 'JS' &&
+      constant.classNode.enclosingLibrary.importUri == Uris.package_js) {
+    assert(constant.fieldValues.length == 1);
+    ir.Constant fieldValue = constant.fieldValues.values.single;
+    if (fieldValue is ir.NullConstant) {
+      return '';
+    } else if (fieldValue is ir.StringConstant) {
+      return fieldValue.value;
     }
   }
   return null;
 }
 
 bool _isAnonymousJsInterop(ir.Constant constant) {
-  if (constant is ir.InstanceConstant) {
-    return constant.classNode.name == '_Anonymous' &&
-        constant.classNode.enclosingLibrary.importUri == Uris.package_js;
-  }
-  return false;
+  return constant is ir.InstanceConstant &&
+      constant.classNode.name == '_Anonymous' &&
+      constant.classNode.enclosingLibrary.importUri == Uris.package_js;
 }
 
 class PragmaAnnotationData {
diff --git a/pkg/compiler/lib/src/kernel/element_map_impl.dart b/pkg/compiler/lib/src/kernel/element_map_impl.dart
index 420a641..2d04f00 100644
--- a/pkg/compiler/lib/src/kernel/element_map_impl.dart
+++ b/pkg/compiler/lib/src/kernel/element_map_impl.dart
@@ -1936,14 +1936,15 @@
   CommonElements get _commonElements => _elementMap.commonElements;
 
   @override
-  void resolveNativeMember(MemberEntity element) {
+  void resolveNativeMember(
+      MemberEntity element, IrAnnotationData annotationData) {
     bool isJsInterop = _isJsInteropMember(element);
     if (element.isFunction ||
         element.isConstructor ||
         element.isGetter ||
         element.isSetter) {
       FunctionEntity method = element;
-      bool isNative = _processMethodAnnotations(method);
+      bool isNative = _processMethodAnnotations(method, annotationData);
       if (isNative || isJsInterop) {
         NativeBehavior behavior =
             _computeNativeMethodBehavior(method, isJsInterop: isJsInterop);
@@ -1951,7 +1952,7 @@
       }
     } else if (element.isField) {
       FieldEntity field = element;
-      bool isNative = _processFieldAnnotations(field);
+      bool isNative = _processFieldAnnotations(field, annotationData);
       if (isNative || isJsInterop) {
         NativeBehavior fieldLoadBehavior =
             _computeNativeFieldLoadBehavior(field, isJsInterop: isJsInterop);
@@ -1966,17 +1967,18 @@
 
   /// Process the potentially native [field]. Adds information from metadata
   /// attributes. Returns `true` of [method] is native.
-  bool _processFieldAnnotations(covariant FieldEntity element) {
+  bool _processFieldAnnotations(
+      FieldEntity element, IrAnnotationData annotationData) {
     if (element.isInstanceMember &&
         _nativeBasicData.isNativeClass(element.enclosingClass)) {
       // Exclude non-instance (static) fields - they are not really native and
       // are compiled as isolate globals.  Access of a property of a constructor
       // function or a non-method property in the prototype chain, must be coded
       // using a JS-call.
-      _setNativeName(element);
+      _setNativeName(element, annotationData);
       return true;
     } else {
-      String name = _findJsNameFromAnnotation(element);
+      String name = _findJsNameFromAnnotation(element, annotationData);
       if (name != null) {
         failedAt(
             element,
@@ -1989,12 +1991,13 @@
 
   /// Process the potentially native [method]. Adds information from metadata
   /// attributes. Returns `true` of [method] is native.
-  bool _processMethodAnnotations(covariant FunctionEntity method) {
-    if (_isNativeMethod(method)) {
+  bool _processMethodAnnotations(
+      FunctionEntity method, IrAnnotationData annotationData) {
+    if (_isNativeMethod(method, annotationData)) {
       if (method.isStatic) {
-        _setNativeNameForStaticMethod(method);
+        _setNativeNameForStaticMethod(method, annotationData);
       } else {
-        _setNativeName(method);
+        _setNativeName(method, annotationData);
       }
       return true;
     }
@@ -2003,9 +2006,9 @@
 
   /// Sets the native name of [element], either from an annotation, or
   /// defaulting to the Dart name.
-  void _setNativeName(MemberEntity element) {
-    String name = _findJsNameFromAnnotation(element);
-    if (name == null) name = element.name;
+  void _setNativeName(MemberEntity element, IrAnnotationData annotationData) {
+    String name = _findJsNameFromAnnotation(element, annotationData);
+    name ??= element.name;
     _nativeDataBuilder.setNativeMemberName(element, name);
   }
 
@@ -2017,9 +2020,10 @@
   ///    use the declared @JSName as the expression
   /// 3. If [element] does not have a @JSName annotation, qualify the name of
   ///    the method with the @Native name of the enclosing class.
-  void _setNativeNameForStaticMethod(FunctionEntity element) {
-    String name = _findJsNameFromAnnotation(element);
-    if (name == null) name = element.name;
+  void _setNativeNameForStaticMethod(
+      FunctionEntity element, IrAnnotationData annotationData) {
+    String name = _findJsNameFromAnnotation(element, annotationData);
+    name ??= element.name;
     if (_isIdentifier(name)) {
       List<String> nativeNames =
           _nativeBasicData.getNativeTagsOfClass(element.enclosingClass);
@@ -2040,16 +2044,21 @@
 
   /// Returns the JSName annotation string or `null` if no JSName annotation is
   /// present.
-  String _findJsNameFromAnnotation(MemberEntity element) {
-    String jsName = null;
-    for (ConstantValue value
-        in _elementEnvironment.getMemberMetadata(element)) {
-      String name = readAnnotationName(
-          element, value, _commonElements.annotationJSNameClass);
-      if (jsName == null) {
-        jsName = name;
-      } else if (name != null) {
-        failedAt(element, 'Too many JSName annotations: ${value.toDartText()}');
+  String _findJsNameFromAnnotation(
+      MemberEntity element, IrAnnotationData annotationData) {
+    String jsName =
+        annotationData.getNativeMemberName(_elementMap.getMemberNode(element));
+    if (jsName == null) {
+      for (ConstantValue value
+          in _elementEnvironment.getMemberMetadata(element)) {
+        String name = readAnnotationName(
+            element, value, _commonElements.annotationJSNameClass);
+        if (jsName == null) {
+          jsName = name;
+        } else if (name != null) {
+          failedAt(
+              element, 'Too many JSName annotations: ${value.toDartText()}');
+        }
       }
     }
     return jsName;
@@ -2074,9 +2083,11 @@
         isJsInterop: isJsInterop);
   }
 
-  bool _isNativeMethod(covariant KFunction function) {
+  bool _isNativeMethod(
+      covariant KFunction function, IrAnnotationData annotationData) {
     if (!maybeEnableNative(function.library.canonicalUri)) return false;
     ir.Member node = _elementMap.getMemberNode(function);
+    if (annotationData.hasNativeBody(node)) return true;
     return node.annotations.any((ir.Expression expression) {
       return expression is ir.ConstructorInvocation &&
           _elementMap.getInterfaceType(expression.constructedType) ==
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index db960ce..442a6c6 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -57,6 +57,7 @@
   final Map<MemberEntity, ClosureScopeModel> closureModels = {};
 
   ModularStrategy _modularStrategy;
+  IrAnnotationData _irAnnotationData;
 
   KernelFrontEndStrategy(this._compilerTask, this._options,
       DiagnosticReporter reporter, env.Environment environment) {
@@ -69,8 +70,9 @@
   @override
   void registerLoadedLibraries(KernelResult kernelResult) {
     _elementMap.addComponent(kernelResult.component);
-    _annotationProcessor = new KernelAnnotationProcessor(elementMap,
-        nativeBasicDataBuilder, processAnnotations(kernelResult.component));
+    _irAnnotationData = processAnnotations(kernelResult.component);
+    _annotationProcessor = new KernelAnnotationProcessor(
+        elementMap, nativeBasicDataBuilder, _irAnnotationData);
   }
 
   ModularStrategy get modularStrategyForTesting => _modularStrategy;
@@ -173,7 +175,8 @@
         closureModels,
         impactCache,
         fieldAnalysis,
-        _modularStrategy);
+        _modularStrategy,
+        _irAnnotationData);
   }
 
   ClassQueries createClassQueries() {
@@ -196,6 +199,7 @@
   final Map<Entity, WorldImpact> _impactCache;
   final KFieldAnalysis _fieldAnalysis;
   final ModularStrategy _modularStrategy;
+  final IrAnnotationData _irAnnotationData;
 
   KernelWorkItemBuilder(
       this._compilerTask,
@@ -207,7 +211,8 @@
       this._closureModels,
       this._impactCache,
       this._fieldAnalysis,
-      this._modularStrategy)
+      this._modularStrategy,
+      this._irAnnotationData)
       : _nativeMemberResolver = new KernelNativeMemberResolver(
             _elementMap, nativeBasicData, nativeDataBuilder);
 
@@ -223,7 +228,8 @@
         _closureModels,
         _impactCache,
         _fieldAnalysis,
-        _modularStrategy);
+        _modularStrategy,
+        _irAnnotationData);
   }
 }
 
@@ -238,6 +244,7 @@
   final Map<Entity, WorldImpact> _impactCache;
   final KFieldAnalysis _fieldAnalysis;
   final ModularStrategy _modularStrategy;
+  final IrAnnotationData _irAnnotationData;
 
   KernelWorkItem(
       this._compilerTask,
@@ -249,12 +256,13 @@
       this._closureModels,
       this._impactCache,
       this._fieldAnalysis,
-      this._modularStrategy);
+      this._modularStrategy,
+      this._irAnnotationData);
 
   @override
   WorldImpact run() {
     return _compilerTask.measure(() {
-      _nativeMemberResolver.resolveNativeMember(element);
+      _nativeMemberResolver.resolveNativeMember(element, _irAnnotationData);
       ir.Member node = _elementMap.getMemberNode(element);
 
       List<PragmaAnnotationData> pragmaAnnotationData =
diff --git a/pkg/compiler/lib/src/native/resolver.dart b/pkg/compiler/lib/src/native/resolver.dart
index 2b4ad8a..49b45d8 100644
--- a/pkg/compiler/lib/src/native/resolver.dart
+++ b/pkg/compiler/lib/src/native/resolver.dart
@@ -6,12 +6,14 @@
 import '../common_elements.dart' show KElementEnvironment;
 import '../constants/values.dart';
 import '../elements/entities.dart';
+import '../ir/annotations.dart';
 import '../js_backend/native_data.dart';
 
 /// Interface for computing native members.
 abstract class NativeMemberResolver {
   /// Computes whether [element] is native or JsInterop.
-  void resolveNativeMember(MemberEntity element);
+  void resolveNativeMember(
+      MemberEntity element, IrAnnotationData annotationData);
 }
 
 /// Determines all native classes in a set of libraries.
diff --git a/tests/compiler/dart2js/model/cfe_annotations_test.dart b/tests/compiler/dart2js/model/cfe_annotations_test.dart
index 0cdcd67..ac7a720 100644
--- a/tests/compiler/dart2js/model/cfe_annotations_test.dart
+++ b/tests/compiler/dart2js/model/cfe_annotations_test.dart
@@ -43,6 +43,17 @@
 method4() {}
 
 main() {
+  method1();
+  method2();
+  method3();
+  method4();
+  new JsClass1()..jsMethod1()..jsMethod2();
+  new JsClass2();
+  jsMethod3();
+  new NativeClass1()..nativeMethod()..nativeField;
+  new NativeClass2()..nativeField;
+  new NativeClass3()..nativeGetter;
+  nativeMethod();
 }
 ''',
   '$pathPrefix/jslib1.dart': '''
@@ -53,11 +64,11 @@
 import 'package:js/js.dart';
 
 @JS('JsInteropClass1')
-class Class1 {
+class JsClass1 {
   @JS('jsInteropMethod1')
-  external method1();
+  external jsMethod1();
   
-  external method2();
+  external jsMethod2();
 }
 
 ''',
@@ -70,35 +81,56 @@
 
 @JS()
 @anonymous
-class Class2 {
+class JsClass2 {
 }
 
 @JS('jsInteropMethod3')
-external method3();
+external jsMethod3();
 ''',
   '$pathPrefix/nativelib.dart': '''
+library lib3; 
+ 
 import 'dart:_js_helper';
 
-@Native('NativeClass1')
-class Class1 {
+@Native('Class1')
+class NativeClass1 {
+  @JSName('field1')
+  var nativeField;
+  
+  @JSName('method1')
+  nativeMethod() native;
 }
 
-@Native('NativeClass2,!nonleaf')
-class Class2 {
+@Native('Class2,!nonleaf')
+class NativeClass2 {
+  @JSName('field2')
+  var nativeField;
 }
 
-@Native('NativeClass3a,NativeClass3b')
-class Class3 {
+@Native('Class3a,Class3b')
+class NativeClass3 {
+  
+  @JSName('method2')
+  get nativeGetter native;
 }
 
-
+@JSName('method3')
+nativeMethod() native;
 ''',
 };
 
 const Map<String, String> expectedNativeClassNames = {
-  '$pathPrefix/nativelib.dart::Class1': 'NativeClass1',
-  '$pathPrefix/nativelib.dart::Class2': 'NativeClass2,!nonleaf',
-  '$pathPrefix/nativelib.dart::Class3': 'NativeClass3a,NativeClass3b',
+  '$pathPrefix/nativelib.dart::NativeClass1': 'Class1',
+  '$pathPrefix/nativelib.dart::NativeClass2': 'Class2,!nonleaf',
+  '$pathPrefix/nativelib.dart::NativeClass3': 'Class3a,Class3b',
+};
+
+const Map<String, String> expectedNativeMemberNames = {
+  '$pathPrefix/nativelib.dart::NativeClass1::nativeField': 'field1',
+  '$pathPrefix/nativelib.dart::NativeClass1::nativeMethod': 'method1',
+  '$pathPrefix/nativelib.dart::NativeClass2::nativeField': 'field2',
+  '$pathPrefix/nativelib.dart::NativeClass3::nativeGetter': 'method2',
+  '$pathPrefix/nativelib.dart::nativeMethod': 'method3',
 };
 
 const Map<String, String> expectedJsInteropLibraryNames = {
@@ -107,17 +139,17 @@
 };
 
 const Map<String, String> expectedJsInteropClassNames = {
-  '$pathPrefix/jslib1.dart::Class1': 'JsInteropClass1',
-  '$pathPrefix/jslib2.dart::Class2': '',
+  '$pathPrefix/jslib1.dart::JsClass1': 'JsInteropClass1',
+  '$pathPrefix/jslib2.dart::JsClass2': '',
 };
 
 const Map<String, String> expectedJsInteropMemberNames = {
-  '$pathPrefix/jslib1.dart::Class1::method1': 'jsInteropMethod1',
-  '$pathPrefix/jslib2.dart::method3': 'jsInteropMethod3',
+  '$pathPrefix/jslib1.dart::JsClass1::jsMethod1': 'jsInteropMethod1',
+  '$pathPrefix/jslib2.dart::jsMethod3': 'jsInteropMethod3',
 };
 
 const Set<String> expectedAnonymousJsInteropClasses = {
-  '$pathPrefix/jslib2.dart::Class2',
+  '$pathPrefix/jslib2.dart::JsClass2',
 };
 
 const Set<String> expectedNoInlineMethods = {
@@ -163,12 +195,13 @@
       }
 
       void testMember(String idPrefix, ir.Member member,
-          {bool implicitJsInteropMember}) {
+          {bool implicitJsInteropMember, bool implicitNativeMember}) {
         String memberId = '$idPrefix::${member.name.name}';
         MemberEntity memberEntity = elementMap.getMember(member);
 
         String expectedJsInteropMemberName =
             expectedJsInteropMemberNames[memberId];
+        String expectedNativeMemberName = expectedNativeMemberNames[memberId];
         Set<String> expectedPragmaNames = {};
         if (expectedNoInlineMethods.contains(memberId)) {
           expectedPragmaNames.add('dart2js:noInline');
@@ -182,6 +215,11 @@
               annotationData.getJsInteropMemberName(member),
               "Unexpected js interop member name from IR for $member");
 
+          Expect.equals(
+              expectedNativeMemberName,
+              annotationData.getNativeMemberName(member),
+              "Unexpected js interop member name from IR for $member");
+
           List<PragmaAnnotationData> pragmaAnnotations =
               annotationData.getMemberPragmaAnnotationData(member);
           Set<String> pragmaNames =
@@ -202,6 +240,22 @@
                 : null,
             nativeData.getJsInteropMemberName(memberEntity),
             "Unexpected js interop member name from native data for $member");
+
+        bool isNativeMember =
+            implicitNativeMember || expectedNativeMemberName != null;
+        Expect.equals(
+            isNativeMember || isJsInteropMember,
+            nativeData.isNativeMember(memberEntity),
+            "Unexpected native member result from native data for $member");
+        Expect.equals(
+            isNativeMember
+                ? expectedNativeMemberName ?? memberEntity.name
+                : (isJsInteropMember
+                    ? expectedJsInteropMemberName ?? memberEntity.name
+                    : null),
+            nativeData.getFixedBackendName(memberEntity),
+            "Unexpected fixed backend name from native data for $member");
+
         List<PragmaAnnotationData> pragmaAnnotations = frontendStrategy
             .modularStrategyForTesting
             .getPragmaAnnotationData(member);
@@ -293,20 +347,24 @@
             for (ir.Member member in cls.members) {
               testMember(clsId, member,
                   implicitJsInteropMember:
-                      nativeData.isJsInteropClass(classEntity));
+                      nativeData.isJsInteropClass(classEntity),
+                  implicitNativeMember: member is! ir.Constructor &&
+                      nativeData.isNativeClass(classEntity) &&
+                      !nativeData.isJsInteropClass(classEntity));
             }
           }
           for (ir.Member member in library.members) {
-            testMember(libraryId, member, implicitJsInteropMember: false);
+            testMember(libraryId, member,
+                implicitJsInteropMember: false, implicitNativeMember: false);
           }
         }
       }
     }
 
-    print('test annotations from IR');
-    await runTest(useIr: true);
-
     print('test annotations from K-model');
     await runTest(useIr: false);
+
+    print('test annotations from IR');
+    await runTest(useIr: true);
   });
 }