Support @Creates and @Returns from ir constants

Change-Id: I5afa43354f70e42dad93a568afabb14d2004cf52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96784
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/ir/annotations.dart b/pkg/compiler/lib/src/ir/annotations.dart
index 3119c2c..1cd8bcf 100644
--- a/pkg/compiler/lib/src/ir/annotations.dart
+++ b/pkg/compiler/lib/src/ir/annotations.dart
@@ -9,6 +9,8 @@
   Map<ir.Class, String> _nativeClassNames = {};
   Set<ir.Member> _nativeMembers = {};
   Map<ir.Member, String> _nativeMemberNames = {};
+  Map<ir.Member, List<String>> _createsAnnotations = {};
+  Map<ir.Member, List<String>> _returnsAnnotations = {};
 
   Map<ir.Library, String> _jsInteropLibraryNames = {};
   Map<ir.Class, String> _jsInteropClassNames = {};
@@ -26,6 +28,16 @@
   // Returns the text from the `@JSName(<text>)` annotation of [node], if any.
   String getNativeMemberName(ir.Member node) => _nativeMemberNames[node];
 
+  // Returns a list of the text from the `@Creates(<text>)` annotation of
+  // [node].
+  List<String> getCreatesAnnotations(ir.Member node) =>
+      _createsAnnotations[node] ?? const [];
+
+  // Returns a list of the text from the `@Returns(<text>)` annotation of
+  // [node].
+  List<String> getReturnsAnnotations(ir.Member node) =>
+      _returnsAnnotations[node] ?? const [];
+
   // Returns the text from the `@JS(<text>)` annotation of [node], if any.
   String getJsInteropLibraryName(ir.Library node) =>
       _jsInteropLibraryNames[node];
@@ -50,6 +62,8 @@
 
   void processMember(ir.Member member) {
     List<PragmaAnnotationData> pragmaAnnotations;
+    List<String> createsAnnotations;
+    List<String> returnsAnnotations;
     for (ir.Expression annotation in member.annotations) {
       if (annotation is ir.ConstantExpression) {
         ir.Constant constant = annotation.constant;
@@ -69,6 +83,22 @@
           data._nativeMemberNames[member] = nativeName;
         }
 
+        String creates = _getCreatesAnnotation(constant);
+        if (creates != null) {
+          if (createsAnnotations == null) {
+            data._createsAnnotations[member] = createsAnnotations = [];
+          }
+          createsAnnotations.add(creates);
+        }
+
+        String returns = _getReturnsAnnotation(constant);
+        if (returns != null) {
+          if (returnsAnnotations == null) {
+            data._returnsAnnotations[member] = returnsAnnotations = [];
+          }
+          returnsAnnotations.add(returns);
+        }
+
         PragmaAnnotationData pragmaAnnotation = _getPragmaAnnotation(constant);
         if (pragmaAnnotation != null) {
           if (pragmaAnnotations == null) {
@@ -163,6 +193,32 @@
   return null;
 }
 
+String _getCreatesAnnotation(ir.Constant constant) {
+  if (constant is ir.InstanceConstant &&
+      constant.classNode.name == 'Creates' &&
+      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 _getReturnsAnnotation(ir.Constant constant) {
+  if (constant is ir.InstanceConstant &&
+      constant.classNode.name == 'Returns' &&
+      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 &&
       constant.classNode.name == 'JS' &&
diff --git a/pkg/compiler/lib/src/kernel/element_map.dart b/pkg/compiler/lib/src/kernel/element_map.dart
index 3a22be9..d69ff52 100644
--- a/pkg/compiler/lib/src/kernel/element_map.dart
+++ b/pkg/compiler/lib/src/kernel/element_map.dart
@@ -147,6 +147,7 @@
 
   /// Computes the native behavior for reading the native [field].
   NativeBehavior getNativeBehaviorForFieldLoad(ir.Field field,
+      Iterable<String> createsAnnotations, Iterable<String> returnsAnnotations,
       {bool isJsInterop});
 
   /// Computes the native behavior for writing to the native [field].
@@ -155,6 +156,7 @@
   /// Computes the native behavior for calling the function or constructor
   /// [member].
   NativeBehavior getNativeBehaviorForMethod(ir.Member member,
+      Iterable<String> createsAnnotations, Iterable<String> returnsAnnotations,
       {bool isJsInterop});
 
   /// Compute the kind of foreign helper function called by [node], if any.
diff --git a/pkg/compiler/lib/src/kernel/element_map_impl.dart b/pkg/compiler/lib/src/kernel/element_map_impl.dart
index b289b50..6f2491d 100644
--- a/pkg/compiler/lib/src/kernel/element_map_impl.dart
+++ b/pkg/compiler/lib/src/kernel/element_map_impl.dart
@@ -1525,14 +1525,11 @@
   // TODO(johnniwinther): Cache this for later use.
   @override
   NativeBehavior getNativeBehaviorForFieldLoad(ir.Field field,
+      Iterable<String> createsAnnotations, Iterable<String> returnsAnnotations,
       {bool isJsInterop}) {
     DartType type = getDartType(field.type);
-    List<ConstantValue> metadata = getMetadata(field.annotations);
-    return nativeBehaviorBuilder.buildFieldLoadBehavior(
-        type,
-        getCreatesAnnotations(reporter, commonElements, metadata),
-        getReturnsAnnotations(reporter, commonElements, metadata),
-        typeLookup(resolveAsRaw: false),
+    return nativeBehaviorBuilder.buildFieldLoadBehavior(type,
+        createsAnnotations, returnsAnnotations, typeLookup(resolveAsRaw: false),
         isJsInterop: isJsInterop);
   }
 
@@ -1548,6 +1545,7 @@
   // TODO(johnniwinther): Cache this for later use.
   @override
   NativeBehavior getNativeBehaviorForMethod(ir.Member member,
+      Iterable<String> createsAnnotations, Iterable<String> returnsAnnotations,
       {bool isJsInterop}) {
     DartType type;
     if (member is ir.Procedure) {
@@ -1557,12 +1555,8 @@
     } else {
       failedAt(CURRENT_ELEMENT_SPANNABLE, "Unexpected method node $member.");
     }
-    List<ConstantValue> metadata = getMetadata(member.annotations);
-    return nativeBehaviorBuilder.buildMethodBehavior(
-        type,
-        getCreatesAnnotations(reporter, commonElements, metadata),
-        getReturnsAnnotations(reporter, commonElements, metadata),
-        typeLookup(resolveAsRaw: false),
+    return nativeBehaviorBuilder.buildMethodBehavior(type, createsAnnotations,
+        returnsAnnotations, typeLookup(resolveAsRaw: false),
         isJsInterop: isJsInterop);
   }
 
@@ -1974,7 +1968,7 @@
 class KernelNativeMemberResolver implements NativeMemberResolver {
   static final RegExp _identifier = new RegExp(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$');
 
-  final KernelToElementMap _elementMap;
+  final KernelToElementMapImpl _elementMap;
   final NativeBasicData _nativeBasicData;
   final NativeDataBuilder _nativeDataBuilder;
 
@@ -1996,16 +1990,18 @@
       FunctionEntity method = element;
       bool isNative = _processMethodAnnotations(method, annotationData);
       if (isNative || isJsInterop) {
-        NativeBehavior behavior =
-            _computeNativeMethodBehavior(method, isJsInterop: isJsInterop);
+        NativeBehavior behavior = _computeNativeMethodBehavior(
+            method, annotationData,
+            isJsInterop: isJsInterop);
         _nativeDataBuilder.setNativeMethodBehavior(method, behavior);
       }
     } else if (element.isField) {
       FieldEntity field = element;
       bool isNative = _processFieldAnnotations(field, annotationData);
       if (isNative || isJsInterop) {
-        NativeBehavior fieldLoadBehavior =
-            _computeNativeFieldLoadBehavior(field, isJsInterop: isJsInterop);
+        NativeBehavior fieldLoadBehavior = _computeNativeFieldLoadBehavior(
+            field, annotationData,
+            isJsInterop: isJsInterop);
         NativeBehavior fieldStoreBehavior =
             _computeNativeFieldStoreBehavior(field);
         _nativeDataBuilder.setNativeFieldLoadBehavior(field, fieldLoadBehavior);
@@ -2096,9 +2092,11 @@
   /// present.
   String _findJsNameFromAnnotation(
       MemberEntity element, IrAnnotationData annotationData) {
-    String jsName =
-        annotationData.getNativeMemberName(_elementMap.getMemberNode(element));
-    if (jsName == null) {
+    String jsName;
+    if (annotationData != null) {
+      jsName = annotationData
+          .getNativeMemberName(_elementMap.getMemberNode(element));
+    } else {
       for (ConstantValue value
           in _elementEnvironment.getMemberMetadata(element)) {
         String name = readAnnotationName(
@@ -2119,17 +2117,47 @@
     return _elementMap.getNativeBehaviorForFieldStore(node);
   }
 
-  NativeBehavior _computeNativeFieldLoadBehavior(covariant KField field,
+  NativeBehavior _computeNativeFieldLoadBehavior(
+      KField field, IrAnnotationData annotationData,
       {bool isJsInterop}) {
     ir.Field node = _elementMap.getMemberNode(field);
-    return _elementMap.getNativeBehaviorForFieldLoad(node,
+    Iterable<String> createsAnnotations;
+    Iterable<String> returnsAnnotations;
+    if (annotationData != null) {
+      createsAnnotations = annotationData.getCreatesAnnotations(node);
+      returnsAnnotations = annotationData.getReturnsAnnotations(node);
+    } else {
+      List<ConstantValue> metadata =
+          _elementEnvironment.getMemberMetadata(field);
+      createsAnnotations = getCreatesAnnotations(
+          _elementMap.reporter, _elementMap.commonElements, metadata);
+      returnsAnnotations = getReturnsAnnotations(
+          _elementMap.reporter, _elementMap.commonElements, metadata);
+    }
+    return _elementMap.getNativeBehaviorForFieldLoad(
+        node, createsAnnotations, returnsAnnotations,
         isJsInterop: isJsInterop);
   }
 
-  NativeBehavior _computeNativeMethodBehavior(covariant KFunction function,
+  NativeBehavior _computeNativeMethodBehavior(
+      KFunction function, IrAnnotationData annotationData,
       {bool isJsInterop}) {
     ir.Member node = _elementMap.getMemberNode(function);
-    return _elementMap.getNativeBehaviorForMethod(node,
+    Iterable<String> createsAnnotations;
+    Iterable<String> returnsAnnotations;
+    if (annotationData != null) {
+      createsAnnotations = annotationData.getCreatesAnnotations(node);
+      returnsAnnotations = annotationData.getReturnsAnnotations(node);
+    } else {
+      List<ConstantValue> metadata =
+          _elementEnvironment.getMemberMetadata(function);
+      createsAnnotations = getCreatesAnnotations(
+          _elementMap.reporter, _elementMap.commonElements, metadata);
+      returnsAnnotations = getReturnsAnnotations(
+          _elementMap.reporter, _elementMap.commonElements, metadata);
+    }
+    return _elementMap.getNativeBehaviorForMethod(
+        node, createsAnnotations, returnsAnnotations,
         isJsInterop: isJsInterop);
   }
 
@@ -2137,12 +2165,15 @@
       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) ==
-              _commonElements.externalNameType;
-    });
+    if (annotationData != null) {
+      return annotationData.hasNativeBody(node);
+    } else {
+      return node.annotations.any((ir.Expression expression) {
+        return expression is ir.ConstructorInvocation &&
+            _elementMap.getInterfaceType(expression.constructedType) ==
+                _commonElements.externalNameType;
+      });
+    }
   }
 
   bool _isJsInteropMember(MemberEntity element) {
diff --git a/pkg/compiler/lib/src/kernel/kernel_impact.dart b/pkg/compiler/lib/src/kernel/kernel_impact.dart
index f32c305..7fe6670 100644
--- a/pkg/compiler/lib/src/kernel/kernel_impact.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_impact.dart
@@ -21,6 +21,7 @@
 import '../ir/util.dart';
 import '../js_backend/annotations.dart';
 import '../js_backend/native_data.dart';
+import '../native/behavior.dart';
 import '../options.dart';
 import '../resolution/registry.dart' show ResolutionWorldImpactBuilder;
 import '../universe/call_structure.dart';
@@ -150,9 +151,18 @@
     if (field.isInstanceMember &&
         elementMap.isNativeClass(field.enclosingClass)) {
       MemberEntity member = elementMap.getMember(field);
+      // TODO(johnniwinther): NativeDataBuilder already has the native behavior
+      // at this point. Use that instead.
       bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
-      impactBuilder.registerNativeData(elementMap
-          .getNativeBehaviorForFieldLoad(field, isJsInterop: isJsInterop));
+      List<ConstantValue> metadata =
+          elementMap.elementEnvironment.getMemberMetadata(member);
+      Iterable<String> createsAnnotations =
+          getCreatesAnnotations(reporter, commonElements, metadata);
+      Iterable<String> returnsAnnotations =
+          getReturnsAnnotations(reporter, commonElements, metadata);
+      impactBuilder.registerNativeData(elementMap.getNativeBehaviorForFieldLoad(
+          field, createsAnnotations, returnsAnnotations,
+          isJsInterop: isJsInterop));
       impactBuilder
           .registerNativeData(elementMap.getNativeBehaviorForFieldStore(field));
     }
@@ -162,9 +172,18 @@
   void registerConstructorNode(ir.Constructor constructor) {
     MemberEntity member = elementMap.getMember(constructor);
     if (constructor.isExternal && !commonElements.isForeignHelper(member)) {
+      // TODO(johnniwinther): NativeDataBuilder already has the native behavior
+      // at this point. Use that instead.
       bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
-      impactBuilder.registerNativeData(elementMap
-          .getNativeBehaviorForMethod(constructor, isJsInterop: isJsInterop));
+      List<ConstantValue> metadata =
+          elementMap.elementEnvironment.getMemberMetadata(member);
+      Iterable<String> createsAnnotations =
+          getCreatesAnnotations(reporter, commonElements, metadata);
+      Iterable<String> returnsAnnotations =
+          getReturnsAnnotations(reporter, commonElements, metadata);
+      impactBuilder.registerNativeData(elementMap.getNativeBehaviorForMethod(
+          constructor, createsAnnotations, returnsAnnotations,
+          isJsInterop: isJsInterop));
     }
   }
 
@@ -199,9 +218,18 @@
   void registerProcedureNode(ir.Procedure procedure) {
     MemberEntity member = elementMap.getMember(procedure);
     if (procedure.isExternal && !commonElements.isForeignHelper(member)) {
+      // TODO(johnniwinther): NativeDataBuilder already has the native behavior
+      // at this point. Use that instead.
       bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
-      impactBuilder.registerNativeData(elementMap
-          .getNativeBehaviorForMethod(procedure, isJsInterop: isJsInterop));
+      List<ConstantValue> metadata =
+          elementMap.elementEnvironment.getMemberMetadata(member);
+      Iterable<String> createsAnnotations =
+          getCreatesAnnotations(reporter, commonElements, metadata);
+      Iterable<String> returnsAnnotations =
+          getReturnsAnnotations(reporter, commonElements, metadata);
+      impactBuilder.registerNativeData(elementMap.getNativeBehaviorForMethod(
+          procedure, createsAnnotations, returnsAnnotations,
+          isJsInterop: isJsInterop));
     }
   }
 
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index 1f265d9..46f5293 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -70,11 +70,15 @@
   @override
   void registerLoadedLibraries(KernelResult kernelResult) {
     _elementMap.addComponent(kernelResult.component);
-    _irAnnotationData = processAnnotations(kernelResult.component);
+    if (useIrAnnotationsDataForTesting) {
+      _irAnnotationData = processAnnotations(kernelResult.component);
+    }
     _annotationProcessor = new KernelAnnotationProcessor(
         elementMap, nativeBasicDataBuilder, _irAnnotationData);
   }
 
+  IrAnnotationData get irAnnotationDataForTesting => _irAnnotationData;
+
   ModularStrategy get modularStrategyForTesting => _modularStrategy;
 
   @override
diff --git a/pkg/compiler/lib/src/kernel/native_basic_data.dart b/pkg/compiler/lib/src/kernel/native_basic_data.dart
index 7aeda4d..745afa5 100644
--- a/pkg/compiler/lib/src/kernel/native_basic_data.dart
+++ b/pkg/compiler/lib/src/kernel/native_basic_data.dart
@@ -20,8 +20,10 @@
 
     elementEnvironment.forEachClass(library, (ClassEntity cls) {
       ir.Class node = elementMap.getClassNode(cls);
-      String annotationName = annotationData.getNativeClassName(node);
-      if (annotationName == null) {
+      String annotationName;
+      if (annotationData != null) {
+        annotationName = annotationData.getNativeClassName(node);
+      } else {
         // TODO(johnniwinther): Remove this branch when we use constants from
         // CFE.
         for (ConstantValue value in elementEnvironment.getClassMetadata(cls)) {
@@ -74,19 +76,27 @@
     KCommonElements commonElements = elementMap.commonElements;
 
     ir.Library libraryNode = elementMap.getLibraryNode(library);
-    String libraryName = annotationData.getJsInteropLibraryName(libraryNode);
-    // TODO(johnniwinther): Remove this when we use constants from CFE.
-    libraryName ??= getJsInteropName(
-        library, elementEnvironment.getLibraryMetadata(library));
+    String libraryName;
+    if (annotationData != null) {
+      libraryName = annotationData.getJsInteropLibraryName(libraryNode);
+    } else {
+      // TODO(johnniwinther): Remove this when we use constants from CFE.
+      libraryName = getJsInteropName(
+          library, elementEnvironment.getLibraryMetadata(library));
+    }
     final bool isExplicitlylyJsLibrary = libraryName != null;
     bool isJsLibrary = isExplicitlylyJsLibrary;
 
     elementEnvironment.forEachLibraryMember(library, (MemberEntity member) {
       ir.Member memberNode = elementMap.getMemberNode(member);
-      String memberName = annotationData.getJsInteropMemberName(memberNode);
-      // TODO(johnniwinther): Remove this when we use constants from CFE.
-      memberName ??= getJsInteropName(
-          library, elementEnvironment.getMemberMetadata(member));
+      String memberName;
+      if (annotationData != null) {
+        memberName = annotationData.getJsInteropMemberName(memberNode);
+      } else {
+        // TODO(johnniwinther): Remove this when we use constants from CFE.
+        memberName = getJsInteropName(
+            library, elementEnvironment.getMemberMetadata(member));
+      }
       if (member.isField) {
         if (memberName != null) {
           // TODO(34174): Disallow js-interop fields.
@@ -121,15 +131,22 @@
     });
 
     elementEnvironment.forEachClass(library, (ClassEntity cls) {
+      Iterable<ConstantValue> metadata;
       ir.Class classNode = elementMap.getClassNode(cls);
-      String className = annotationData.getJsInteropClassName(classNode);
-      Iterable<ConstantValue> metadata =
-          elementEnvironment.getClassMetadata(cls);
-      // TODO(johnniwinther): Remove this when we use constants from CFE.
-      className ??= getJsInteropName(cls, metadata);
+      String className;
+      if (annotationData != null) {
+        className = annotationData.getJsInteropClassName(classNode);
+      } else {
+        metadata = elementEnvironment.getClassMetadata(cls);
+        // TODO(johnniwinther): Remove this when we use constants from CFE.
+        className = getJsInteropName(cls, metadata);
+      }
       if (className != null) {
-        bool isAnonymous = annotationData.isAnonymousJsInteropClass(classNode);
-        if (!isAnonymous) {
+        bool isAnonymous;
+        if (annotationData != null) {
+          isAnonymous = annotationData.isAnonymousJsInteropClass(classNode);
+        } else {
+          isAnonymous = false;
           // TODO(johnniwinther): Remove this branch when we use constants from
           // CFE.
           for (ConstantValue value in metadata) {
@@ -155,12 +172,14 @@
           } else {
             FunctionEntity function = member;
             ir.Member memberNode = elementMap.getMemberNode(member);
-            String memberName =
-                annotationData.getJsInteropMemberName(memberNode);
-            // TODO(johnniwinther): Remove this when we use constants from CFE.
-            memberName ??= getJsInteropName(
-                library, elementEnvironment.getMemberMetadata(function));
-
+            String memberName;
+            if (annotationData != null) {
+              memberName = annotationData.getJsInteropMemberName(memberNode);
+            } else {
+              // TODO(johnniwinther): Remove this when we use constants from CFE.
+              memberName = getJsInteropName(
+                  library, elementEnvironment.getMemberMetadata(function));
+            }
             if (function.isExternal) {
               memberName ??= function.name;
             }
@@ -280,10 +299,14 @@
       // it.
       elementEnvironment.forEachClass(library, (ClassEntity cls) {
         ir.Class classNode = elementMap.getClassNode(cls);
-        String className = annotationData.getJsInteropClassName(classNode);
-        // TODO(johnniwinther): Remove this when we use constants from CFE.
-        className ??=
-            getJsInteropName(cls, elementEnvironment.getClassMetadata(cls));
+        String className;
+        if (annotationData != null) {
+          className = annotationData.getJsInteropClassName(classNode);
+        } else {
+          // TODO(johnniwinther): Remove this when we use constants from CFE.
+          className ??=
+              getJsInteropName(cls, elementEnvironment.getClassMetadata(cls));
+        }
         if (className != null) {
           bool implementsJsJavaScriptObjectClass = false;
           elementEnvironment.forEachSupertype(cls, (InterfaceType supertype) {
diff --git a/tests/compiler/dart2js/model/cfe_annotations_test.dart b/tests/compiler/dart2js/model/cfe_annotations_test.dart
index ac7a720..4f8c619 100644
--- a/tests/compiler/dart2js/model/cfe_annotations_test.dart
+++ b/tests/compiler/dart2js/model/cfe_annotations_test.dart
@@ -52,7 +52,7 @@
   jsMethod3();
   new NativeClass1()..nativeMethod()..nativeField;
   new NativeClass2()..nativeField;
-  new NativeClass3()..nativeGetter;
+  new NativeClass3()..nativeMethod()..nativeGetter;
   nativeMethod();
 }
 ''',
@@ -112,6 +112,10 @@
   
   @JSName('method2')
   get nativeGetter native;
+
+  @Creates('String')
+  @Returns('int')
+  nativeMethod() native;
 }
 
 @JSName('method3')
@@ -133,6 +137,14 @@
   '$pathPrefix/nativelib.dart::nativeMethod': 'method3',
 };
 
+const Map<String, String> expectedCreates = {
+  '$pathPrefix/nativelib.dart::NativeClass3::nativeMethod': 'String',
+};
+
+const Map<String, String> expectedReturns = {
+  '$pathPrefix/nativelib.dart::NativeClass3::nativeMethod': 'int',
+};
+
 const Map<String, String> expectedJsInteropLibraryNames = {
   '$pathPrefix/jslib1.dart': 'lib1',
   '$pathPrefix/jslib2.dart': '',
@@ -189,10 +201,8 @@
       NativeData nativeData =
           compiler.resolutionWorldBuilder.closedWorldForTesting.nativeData;
       ir.Component component = elementMap.env.mainComponent;
-      IrAnnotationData annotationData;
-      if (useIr) {
-        annotationData = processAnnotations(component);
-      }
+      IrAnnotationData annotationData =
+          frontendStrategy.irAnnotationDataForTesting;
 
       void testMember(String idPrefix, ir.Member member,
           {bool implicitJsInteropMember, bool implicitNativeMember}) {
@@ -209,23 +219,45 @@
         if (expectedTryInlineMethods.contains(memberId)) {
           expectedPragmaNames.add('dart2js:tryInline');
         }
+
+        String expectedCreatesText = expectedCreates[memberId];
+        String expectedReturnsText = expectedReturns[memberId];
+
         if (useIr) {
           Expect.equals(
               expectedJsInteropMemberName,
               annotationData.getJsInteropMemberName(member),
-              "Unexpected js interop member name from IR for $member");
+              "Unexpected js interop member name from IR for $member, "
+              "id: $memberId");
 
           Expect.equals(
               expectedNativeMemberName,
               annotationData.getNativeMemberName(member),
-              "Unexpected js interop member name from IR for $member");
+              "Unexpected js interop member name from IR for $member, "
+              "id: $memberId");
 
           List<PragmaAnnotationData> pragmaAnnotations =
               annotationData.getMemberPragmaAnnotationData(member);
           Set<String> pragmaNames =
               pragmaAnnotations.map((d) => d.name).toSet();
           Expect.setEquals(expectedPragmaNames, pragmaNames,
-              "Unexpected pragmas from IR for $member");
+              "Unexpected pragmas from IR for $member, " "id: $memberId");
+
+          List<String> createsAnnotations =
+              annotationData.getCreatesAnnotations(member);
+          Expect.equals(
+              expectedCreatesText,
+              createsAnnotations.isEmpty ? null : createsAnnotations.join(','),
+              "Unexpected create annotations from IR for $member, "
+              "id: $memberId");
+
+          List<String> returnsAnnotations =
+              annotationData.getReturnsAnnotations(member);
+          Expect.equals(
+              expectedReturnsText,
+              returnsAnnotations.isEmpty ? null : returnsAnnotations.join(','),
+              "Unexpected returns annotations from IR for $member, "
+              "id: $memberId");
         }
         bool isJsInteropMember =
             (implicitJsInteropMember && member.isExternal) ||
@@ -233,20 +265,23 @@
         Expect.equals(
             isJsInteropMember,
             nativeData.isJsInteropMember(memberEntity),
-            "Unexpected js interop member result from native data for $member");
+            "Unexpected js interop member result from native data for $member, "
+            "id: $memberId");
         Expect.equals(
             isJsInteropMember
                 ? expectedJsInteropMemberName ?? memberEntity.name
                 : null,
             nativeData.getJsInteropMemberName(memberEntity),
-            "Unexpected js interop member name from native data for $member");
+            "Unexpected js interop member name from native data for $member, "
+            "id: $memberId");
 
         bool isNativeMember =
             implicitNativeMember || expectedNativeMemberName != null;
         Expect.equals(
             isNativeMember || isJsInteropMember,
             nativeData.isNativeMember(memberEntity),
-            "Unexpected native member result from native data for $member");
+            "Unexpected native member result from native data for $member, "
+            "id: $memberId");
         Expect.equals(
             isNativeMember
                 ? expectedNativeMemberName ?? memberEntity.name
@@ -254,14 +289,58 @@
                     ? expectedJsInteropMemberName ?? memberEntity.name
                     : null),
             nativeData.getFixedBackendName(memberEntity),
-            "Unexpected fixed backend name from native data for $member");
+            "Unexpected fixed backend name from native data for $member, "
+            "id: $memberId");
+
+        if (expectedCreatesText != null) {
+          String createsText;
+          if (memberEntity.isField) {
+            createsText = nativeData
+                .getNativeFieldLoadBehavior(memberEntity)
+                .typesInstantiated
+                .join(',');
+          } else {
+            createsText = nativeData
+                .getNativeMethodBehavior(memberEntity)
+                .typesInstantiated
+                .join(',');
+          }
+          Expect.equals(
+              expectedCreatesText,
+              createsText,
+              "Unexpected create annotations from native data for $member, "
+              "id: $memberId");
+        }
+
+        if (expectedReturnsText != null) {
+          String returnsText;
+          if (memberEntity.isField) {
+            returnsText = nativeData
+                .getNativeFieldLoadBehavior(memberEntity)
+                .typesReturned
+                .join(',');
+          } else {
+            returnsText = nativeData
+                .getNativeMethodBehavior(memberEntity)
+                .typesReturned
+                .join(',');
+          }
+          Expect.equals(
+              expectedReturnsText,
+              returnsText,
+              "Unexpected returns annotations from native data for $member, "
+              "id: $memberId");
+        }
 
         List<PragmaAnnotationData> pragmaAnnotations = frontendStrategy
             .modularStrategyForTesting
             .getPragmaAnnotationData(member);
         Set<String> pragmaNames = pragmaAnnotations.map((d) => d.name).toSet();
-        Expect.setEquals(expectedPragmaNames, pragmaNames,
-            "Unexpected pragmas from modular strategy for $member");
+        Expect.setEquals(
+            expectedPragmaNames,
+            pragmaNames,
+            "Unexpected pragmas from modular strategy for $member, "
+            "id: $memberId");
       }
 
       for (ir.Library library in component.libraries) {