Optimize index data structure for search by supertype id.

So, instead of iterating over all subtypes and checking their supertypes
in string lists, we encode the supertype string id into int once, and
search for it in the denormalized supertype list.

This helps for files with really many classes and subtypes, such as
element.dart and as.dart (both interfaces, not implementations).

Before:
1537403233979 <= Computed implemented in 803 ms.
1537403239426 <= Computed implemented in 2518 ms.
1537403241893 <= Computed implemented in 811 ms.
1537403247675 <= Computed implemented in 2523 ms.
1537403249835 <= Computed implemented in 802 ms.
1537403255796 <= Computed implemented in 2478 ms.
1537403258396 <= Computed implemented in 809 ms.
1537403264560 <= Computed implemented in 2526 ms.
1537403267010 <= Computed implemented in 814 ms.
1537403274513 <= Computed implemented in 2491 ms.

R=brianwilkerson@google.com, paulberry@google.com

After:
1537415241048 <= Computed implemented in 421 ms.
1537415244042 <= Computed implemented in 873 ms.
1537415246189 <= Computed implemented in 402 ms.
1537415249342 <= Computed implemented in 853 ms.
1537415251478 <= Computed implemented in 420 ms.
1537415254756 <= Computed implemented in 877 ms.
1537415257278 <= Computed implemented in 492 ms.
1537415260514 <= Computed implemented in 864 ms.
1537415262864 <= Computed implemented in 421 ms.
1537415266170 <= Computed implemented in 888 ms.
Change-Id: I00e3b8d11ecc7da93816bc5575b7b79b91535d50
Reviewed-on: https://dart-review.googlesource.com/75631
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index fc72f2b..3269d69 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -93,7 +93,7 @@
   /**
    * The version of data format, should be incremented on every format change.
    */
-  static const int DATA_VERSION = 69;
+  static const int DATA_VERSION = 70;
 
   /**
    * The number of exception contexts allowed to write. Once this field is
diff --git a/pkg/analyzer/lib/src/dart/analysis/index.dart b/pkg/analyzer/lib/src/dart/analysis/index.dart
index 69dd439..4e620344 100644
--- a/pkg/analyzer/lib/src/dart/analysis/index.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/index.dart
@@ -212,7 +212,7 @@
   /**
    * All subtypes declared in the unit.
    */
-  final List<AnalysisDriverSubtypeBuilder> subtypes = [];
+  final List<_SubtypeInfo> subtypes = [];
 
   /**
    * The [_StringInfo] to use for `null` strings.
@@ -236,12 +236,25 @@
     nameRelations.add(new _NameRelationInfo(nameId, kind, offset, isQualified));
   }
 
+  void addSubtype(String name, List<String> members, List<String> supertypes) {
+    for (var supertype in supertypes) {
+      subtypes.add(
+        new _SubtypeInfo(
+          _getStringInfo(supertype),
+          _getStringInfo(name),
+          members.map(_getStringInfo).toList(),
+        ),
+      );
+    }
+  }
+
   /**
    * Index the [unit] and assemble a new [AnalysisDriverUnitIndexBuilder].
    */
   AnalysisDriverUnitIndexBuilder assemble(CompilationUnit unit) {
     unit.accept(new _IndexContributor(this));
-    // sort strings end set IDs
+
+    // Sort strings and set IDs.
     List<_StringInfo> stringInfoList = stringMap.values.toList();
     stringInfoList.sort((a, b) {
       return a.value.compareTo(b.value);
@@ -249,16 +262,17 @@
     for (int i = 0; i < stringInfoList.length; i++) {
       stringInfoList[i].id = i;
     }
-    // sort elements and set IDs
+
+    // Sort elements and set IDs.
     List<_ElementInfo> elementInfoList = elementMap.values.toList();
     elementInfoList.sort((a, b) {
       int delta;
       delta = a.nameIdUnitMember.id - b.nameIdUnitMember.id;
-      if (delta != null) {
+      if (delta != 0) {
         return delta;
       }
       delta = a.nameIdClassMember.id - b.nameIdClassMember.id;
-      if (delta != null) {
+      if (delta != 0) {
         return delta;
       }
       return a.nameIdParameter.id - b.nameIdParameter.id;
@@ -266,6 +280,7 @@
     for (int i = 0; i < elementInfoList.length; i++) {
       elementInfoList[i].id = i;
     }
+
     // Sort element and name relations.
     elementRelations.sort((a, b) {
       return a.elementInfo.id - b.elementInfo.id;
@@ -273,6 +288,12 @@
     nameRelations.sort((a, b) {
       return a.nameInfo.id - b.nameInfo.id;
     });
+
+    // Sort subtypes by supertypes.
+    subtypes.sort((a, b) {
+      return a.supertype.id - b.supertype.id;
+    });
+
     return new AnalysisDriverUnitIndexBuilder(
         strings: stringInfoList.map((s) => s.value).toList(),
         nullStringId: nullString.id,
@@ -297,7 +318,13 @@
         usedNameOffsets: nameRelations.map((r) => r.offset).toList(),
         usedNameIsQualifiedFlags:
             nameRelations.map((r) => r.isQualified).toList(),
-        subtypes: subtypes);
+        supertypes: subtypes.map((subtype) => subtype.supertype.id).toList(),
+        subtypes: subtypes.map((subtype) {
+          return new AnalysisDriverSubtypeBuilder(
+            name: subtype.name.id,
+            members: subtype.members.map((member) => member.id).toList(),
+          );
+        }).toList());
   }
 
   /**
@@ -806,8 +833,7 @@
     supertypes.sort();
     members.sort();
 
-    assembler.subtypes.add(new AnalysisDriverSubtypeBuilder(
-        name: name, supertypes: supertypes, members: members));
+    assembler.addSubtype(name, members, supertypes);
   }
 
   /**
@@ -938,3 +964,25 @@
 
   _StringInfo(this.value);
 }
+
+/**
+ * Information about a subtype in the index.
+ */
+class _SubtypeInfo {
+  /**
+   * The identifier of a direct supertype.
+   */
+  final _StringInfo supertype;
+
+  /**
+   * The name of the class.
+   */
+  final _StringInfo name;
+
+  /**
+   * The names of defined instance members.
+   */
+  final List<_StringInfo> members;
+
+  _SubtypeInfo(this.supertype, this.name, this.members);
+}
diff --git a/pkg/analyzer/lib/src/dart/analysis/search.dart b/pkg/analyzer/lib/src/dart/analysis/search.dart
index 447d1f7..9d1ff41 100644
--- a/pkg/analyzer/lib/src/dart/analysis/search.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/search.dart
@@ -378,8 +378,6 @@
    */
   Future<List<SubtypeResult>> subtypes(
       {ClassElement type, SubtypeResult subtype}) async {
-    // TODO(brianwilkerson) Determine whether this await is necessary.
-    await null;
     String name;
     String id;
     if (type != null) {
@@ -401,17 +399,8 @@
       for (FileState file in files) {
         AnalysisDriverUnitIndex index = await _driver.getIndex(file.path);
         if (index != null) {
-          for (AnalysisDriverSubtype subtype in index.subtypes) {
-            if (subtype.supertypes.contains(id)) {
-              FileState library = file.library ?? file;
-              results.add(new SubtypeResult(
-                library.uriStr,
-                '${library.uriStr};${file.uriStr};${subtype.name}',
-                subtype.name,
-                subtype.members,
-              ));
-            }
-          }
+          var request = new _IndexRequest(index);
+          request.addSubtypes(id, results, file);
         }
       }
     }
@@ -987,6 +976,32 @@
 
   _IndexRequest(this.index);
 
+  void addSubtypes(
+      String superIdString, List<SubtypeResult> results, FileState file) {
+    var superId = getStringId(superIdString);
+    if (superId == -1) {
+      return;
+    }
+
+    var superIndex = _findFirstOccurrence(index.supertypes, superId);
+    if (superIndex == -1) {
+      return;
+    }
+
+    var library = file.library ?? file;
+    for (; index.supertypes[superIndex] == superId; superIndex++) {
+      var subtype = index.subtypes[superIndex];
+      var name = index.strings[subtype.name];
+      var subId = '${library.uriStr};${file.uriStr};$name';
+      results.add(new SubtypeResult(
+        library.uriStr,
+        subId,
+        name,
+        subtype.members.map((m) => index.strings[m]).toList(),
+      ));
+    }
+  }
+
   /**
    * Return the [element]'s identifier in the [index] or `-1` if the
    * [element] is not referenced in the [index].
diff --git a/pkg/analyzer/lib/src/summary/format.dart b/pkg/analyzer/lib/src/summary/format.dart
index 610ae0d..cda912f 100644
--- a/pkg/analyzer/lib/src/summary/format.dart
+++ b/pkg/analyzer/lib/src/summary/format.dart
@@ -672,47 +672,37 @@
 class AnalysisDriverSubtypeBuilder extends Object
     with _AnalysisDriverSubtypeMixin
     implements idl.AnalysisDriverSubtype {
-  List<String> _members;
-  String _name;
-  List<String> _supertypes;
+  List<int> _members;
+  int _name;
 
   @override
-  List<String> get members => _members ??= <String>[];
+  List<int> get members => _members ??= <int>[];
 
   /**
    * The names of defined instance members.
+   * They are indexes into [AnalysisDriverUnitError.strings] list.
    * The list is sorted in ascending order.
    */
-  void set members(List<String> value) {
+  void set members(List<int> value) {
+    assert(value == null || value.every((e) => e >= 0));
     this._members = value;
   }
 
   @override
-  String get name => _name ??= '';
+  int get name => _name ??= 0;
 
   /**
    * The name of the class.
+   * It is an index into [AnalysisDriverUnitError.strings] list.
    */
-  void set name(String value) {
+  void set name(int value) {
+    assert(value == null || value >= 0);
     this._name = value;
   }
 
-  @override
-  List<String> get supertypes => _supertypes ??= <String>[];
-
-  /**
-   * The identifiers of the direct supertypes.
-   * The list is sorted in ascending order.
-   */
-  void set supertypes(List<String> value) {
-    this._supertypes = value;
-  }
-
-  AnalysisDriverSubtypeBuilder(
-      {List<String> members, String name, List<String> supertypes})
+  AnalysisDriverSubtypeBuilder({List<int> members, int name})
       : _members = members,
-        _name = name,
-        _supertypes = supertypes;
+        _name = name;
 
   /**
    * Flush [informative] data recursively.
@@ -723,49 +713,28 @@
    * Accumulate non-[informative] data into [signature].
    */
   void collectApiSignature(api_sig.ApiSignature signature) {
-    signature.addString(this._name ?? '');
-    if (this._supertypes == null) {
-      signature.addInt(0);
-    } else {
-      signature.addInt(this._supertypes.length);
-      for (var x in this._supertypes) {
-        signature.addString(x);
-      }
-    }
+    signature.addInt(this._name ?? 0);
     if (this._members == null) {
       signature.addInt(0);
     } else {
       signature.addInt(this._members.length);
       for (var x in this._members) {
-        signature.addString(x);
+        signature.addInt(x);
       }
     }
   }
 
   fb.Offset finish(fb.Builder fbBuilder) {
     fb.Offset offset_members;
-    fb.Offset offset_name;
-    fb.Offset offset_supertypes;
     if (!(_members == null || _members.isEmpty)) {
-      offset_members = fbBuilder
-          .writeList(_members.map((b) => fbBuilder.writeString(b)).toList());
-    }
-    if (_name != null) {
-      offset_name = fbBuilder.writeString(_name);
-    }
-    if (!(_supertypes == null || _supertypes.isEmpty)) {
-      offset_supertypes = fbBuilder
-          .writeList(_supertypes.map((b) => fbBuilder.writeString(b)).toList());
+      offset_members = fbBuilder.writeListUint32(_members);
     }
     fbBuilder.startTable();
     if (offset_members != null) {
-      fbBuilder.addOffset(2, offset_members);
+      fbBuilder.addOffset(1, offset_members);
     }
-    if (offset_name != null) {
-      fbBuilder.addOffset(0, offset_name);
-    }
-    if (offset_supertypes != null) {
-      fbBuilder.addOffset(1, offset_supertypes);
+    if (_name != null && _name != 0) {
+      fbBuilder.addUint32(0, _name);
     }
     return fbBuilder.endTable();
   }
@@ -788,29 +757,21 @@
 
   _AnalysisDriverSubtypeImpl(this._bc, this._bcOffset);
 
-  List<String> _members;
-  String _name;
-  List<String> _supertypes;
+  List<int> _members;
+  int _name;
 
   @override
-  List<String> get members {
-    _members ??= const fb.ListReader<String>(const fb.StringReader())
-        .vTableGet(_bc, _bcOffset, 2, const <String>[]);
+  List<int> get members {
+    _members ??=
+        const fb.Uint32ListReader().vTableGet(_bc, _bcOffset, 1, const <int>[]);
     return _members;
   }
 
   @override
-  String get name {
-    _name ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 0, '');
+  int get name {
+    _name ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 0, 0);
     return _name;
   }
-
-  @override
-  List<String> get supertypes {
-    _supertypes ??= const fb.ListReader<String>(const fb.StringReader())
-        .vTableGet(_bc, _bcOffset, 1, const <String>[]);
-    return _supertypes;
-  }
 }
 
 abstract class _AnalysisDriverSubtypeMixin
@@ -819,8 +780,7 @@
   Map<String, Object> toJson() {
     Map<String, Object> _result = <String, Object>{};
     if (members.isNotEmpty) _result["members"] = members;
-    if (name != '') _result["name"] = name;
-    if (supertypes.isNotEmpty) _result["supertypes"] = supertypes;
+    if (name != 0) _result["name"] = name;
     return _result;
   }
 
@@ -828,7 +788,6 @@
   Map<String, Object> toMap() => {
         "members": members,
         "name": name,
-        "supertypes": supertypes,
       };
 
   @override
@@ -1048,6 +1007,7 @@
   int _nullStringId;
   List<String> _strings;
   List<AnalysisDriverSubtypeBuilder> _subtypes;
+  List<int> _supertypes;
   List<int> _unitLibraryUris;
   List<int> _unitUnitUris;
   List<bool> _usedElementIsQualifiedFlags;
@@ -1164,6 +1124,20 @@
   }
 
   @override
+  List<int> get supertypes => _supertypes ??= <int>[];
+
+  /**
+   * The identifiers of supertypes of elements at corresponding indexes
+   * in [subtypes].  They are indexes into [strings] list. The list is sorted
+   * in ascending order.  There might be more than one element with the same
+   * value if there is more than one subtype of this supertype.
+   */
+  void set supertypes(List<int> value) {
+    assert(value == null || value.every((e) => e >= 0));
+    this._supertypes = value;
+  }
+
+  @override
   List<int> get unitLibraryUris => _unitLibraryUris ??= <int>[];
 
   /**
@@ -1304,6 +1278,7 @@
       int nullStringId,
       List<String> strings,
       List<AnalysisDriverSubtypeBuilder> subtypes,
+      List<int> supertypes,
       List<int> unitLibraryUris,
       List<int> unitUnitUris,
       List<bool> usedElementIsQualifiedFlags,
@@ -1323,6 +1298,7 @@
         _nullStringId = nullStringId,
         _strings = strings,
         _subtypes = subtypes,
+        _supertypes = supertypes,
         _unitLibraryUris = unitLibraryUris,
         _unitUnitUris = unitUnitUris,
         _usedElementIsQualifiedFlags = usedElementIsQualifiedFlags,
@@ -1483,6 +1459,14 @@
         signature.addBool(x);
       }
     }
+    if (this._supertypes == null) {
+      signature.addInt(0);
+    } else {
+      signature.addInt(this._supertypes.length);
+      for (var x in this._supertypes) {
+        signature.addInt(x);
+      }
+    }
     if (this._subtypes == null) {
       signature.addInt(0);
     } else {
@@ -1506,6 +1490,7 @@
     fb.Offset offset_elementUnits;
     fb.Offset offset_strings;
     fb.Offset offset_subtypes;
+    fb.Offset offset_supertypes;
     fb.Offset offset_unitLibraryUris;
     fb.Offset offset_unitUnitUris;
     fb.Offset offset_usedElementIsQualifiedFlags;
@@ -1547,6 +1532,9 @@
       offset_subtypes = fbBuilder
           .writeList(_subtypes.map((b) => b.finish(fbBuilder)).toList());
     }
+    if (!(_supertypes == null || _supertypes.isEmpty)) {
+      offset_supertypes = fbBuilder.writeListUint32(_supertypes);
+    }
     if (!(_unitLibraryUris == null || _unitLibraryUris.isEmpty)) {
       offset_unitLibraryUris = fbBuilder.writeListUint32(_unitLibraryUris);
     }
@@ -1611,7 +1599,10 @@
       fbBuilder.addOffset(0, offset_strings);
     }
     if (offset_subtypes != null) {
-      fbBuilder.addOffset(18, offset_subtypes);
+      fbBuilder.addOffset(19, offset_subtypes);
+    }
+    if (offset_supertypes != null) {
+      fbBuilder.addOffset(18, offset_supertypes);
     }
     if (offset_unitLibraryUris != null) {
       fbBuilder.addOffset(2, offset_unitLibraryUris);
@@ -1680,6 +1671,7 @@
   int _nullStringId;
   List<String> _strings;
   List<idl.AnalysisDriverSubtype> _subtypes;
+  List<int> _supertypes;
   List<int> _unitLibraryUris;
   List<int> _unitUnitUris;
   List<bool> _usedElementIsQualifiedFlags;
@@ -1745,11 +1737,18 @@
   List<idl.AnalysisDriverSubtype> get subtypes {
     _subtypes ??= const fb.ListReader<idl.AnalysisDriverSubtype>(
             const _AnalysisDriverSubtypeReader())
-        .vTableGet(_bc, _bcOffset, 18, const <idl.AnalysisDriverSubtype>[]);
+        .vTableGet(_bc, _bcOffset, 19, const <idl.AnalysisDriverSubtype>[]);
     return _subtypes;
   }
 
   @override
+  List<int> get supertypes {
+    _supertypes ??= const fb.Uint32ListReader()
+        .vTableGet(_bc, _bcOffset, 18, const <int>[]);
+    return _supertypes;
+  }
+
+  @override
   List<int> get unitLibraryUris {
     _unitLibraryUris ??=
         const fb.Uint32ListReader().vTableGet(_bc, _bcOffset, 2, const <int>[]);
@@ -1849,6 +1848,7 @@
     if (strings.isNotEmpty) _result["strings"] = strings;
     if (subtypes.isNotEmpty)
       _result["subtypes"] = subtypes.map((_value) => _value.toJson()).toList();
+    if (supertypes.isNotEmpty) _result["supertypes"] = supertypes;
     if (unitLibraryUris.isNotEmpty)
       _result["unitLibraryUris"] = unitLibraryUris;
     if (unitUnitUris.isNotEmpty) _result["unitUnitUris"] = unitUnitUris;
@@ -1885,6 +1885,7 @@
         "nullStringId": nullStringId,
         "strings": strings,
         "subtypes": subtypes,
+        "supertypes": supertypes,
         "unitLibraryUris": unitLibraryUris,
         "unitUnitUris": unitUnitUris,
         "usedElementIsQualifiedFlags": usedElementIsQualifiedFlags,
diff --git a/pkg/analyzer/lib/src/summary/format.fbs b/pkg/analyzer/lib/src/summary/format.fbs
index a37da95..0f70322 100644
--- a/pkg/analyzer/lib/src/summary/format.fbs
+++ b/pkg/analyzer/lib/src/summary/format.fbs
@@ -987,20 +987,16 @@
 table AnalysisDriverSubtype {
   /**
    * The names of defined instance members.
+   * They are indexes into [AnalysisDriverUnitError.strings] list.
    * The list is sorted in ascending order.
    */
-  members:[string] (id: 2);
+  members:[uint] (id: 1);
 
   /**
    * The name of the class.
+   * It is an index into [AnalysisDriverUnitError.strings] list.
    */
-  name:string (id: 0);
-
-  /**
-   * The identifiers of the direct supertypes.
-   * The list is sorted in ascending order.
-   */
-  supertypes:[string] (id: 1);
+  name:uint (id: 0);
 }
 
 /**
@@ -1089,7 +1085,15 @@
   /**
    * The list of classes declared in the unit.
    */
-  subtypes:[AnalysisDriverSubtype] (id: 18);
+  subtypes:[AnalysisDriverSubtype] (id: 19);
+
+  /**
+   * The identifiers of supertypes of elements at corresponding indexes
+   * in [subtypes].  They are indexes into [strings] list. The list is sorted
+   * in ascending order.  There might be more than one element with the same
+   * value if there is more than one subtype of this supertype.
+   */
+  supertypes:[uint] (id: 18);
 
   /**
    * Each item of this list corresponds to the library URI of a unique library
diff --git a/pkg/analyzer/lib/src/summary/idl.dart b/pkg/analyzer/lib/src/summary/idl.dart
index c9ca5fd..4a9640c 100644
--- a/pkg/analyzer/lib/src/summary/idl.dart
+++ b/pkg/analyzer/lib/src/summary/idl.dart
@@ -134,23 +134,18 @@
 abstract class AnalysisDriverSubtype extends base.SummaryClass {
   /**
    * The names of defined instance members.
-   * The list is sorted in ascending order.
-   */
-  @Id(2)
-  List<String> get members;
-
-  /**
-   * The name of the class.
-   */
-  @Id(0)
-  String get name;
-
-  /**
-   * The identifiers of the direct supertypes.
+   * They are indexes into [AnalysisDriverUnitError.strings] list.
    * The list is sorted in ascending order.
    */
   @Id(1)
-  List<String> get supertypes;
+  List<int> get members;
+
+  /**
+   * The name of the class.
+   * It is an index into [AnalysisDriverUnitError.strings] list.
+   */
+  @Id(0)
+  int get name;
 }
 
 /**
@@ -255,10 +250,19 @@
   /**
    * The list of classes declared in the unit.
    */
-  @Id(18)
+  @Id(19)
   List<AnalysisDriverSubtype> get subtypes;
 
   /**
+   * The identifiers of supertypes of elements at corresponding indexes
+   * in [subtypes].  They are indexes into [strings] list. The list is sorted
+   * in ascending order.  There might be more than one element with the same
+   * value if there is more than one subtype of this supertype.
+   */
+  @Id(18)
+  List<int> get supertypes;
+
+  /**
    * Each item of this list corresponds to the library URI of a unique library
    * specific unit referenced in the index.  It is an index into [strings] list.
    */
diff --git a/pkg/analyzer/test/src/dart/analysis/index_test.dart b/pkg/analyzer/test/src/dart/analysis/index_test.dart
index 179e4c4..e3106d1 100644
--- a/pkg/analyzer/test/src/dart/analysis/index_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/index_test.dart
@@ -1014,27 +1014,20 @@
 }
 ''');
 
-    {
-      AnalysisDriverSubtype X =
-          index.subtypes.singleWhere((t) => t.name == 'X');
-      expect(X.supertypes, ['$libP;A']);
-      expect(X.members, ['field1', 'field2', 'getter1', 'method1', 'setter1']);
-    }
+    expect(index.supertypes, hasLength(6));
+    expect(index.subtypes, hasLength(6));
 
-    {
-      AnalysisDriverSubtype Y =
-          index.subtypes.singleWhere((t) => t.name == 'Y');
-      expect(
-          Y.supertypes, ['dart:core;dart:core;Object', '$libP;B', '$libP;C']);
-      expect(Y.members, ['methodY']);
-    }
-
-    {
-      AnalysisDriverSubtype Z =
-          index.subtypes.singleWhere((t) => t.name == 'Z');
-      expect(Z.supertypes, ['$libP;D', '$libP;E']);
-      expect(Z.members, ['methodZ']);
-    }
+    _assertSubtype(0, 'dart:core;dart:core;Object', 'Y', ['methodY']);
+    _assertSubtype(
+      1,
+      '$libP;A',
+      'X',
+      ['field1', 'field2', 'getter1', 'method1', 'setter1'],
+    );
+    _assertSubtype(2, '$libP;B', 'Y', ['methodY']);
+    _assertSubtype(3, '$libP;C', 'Y', ['methodY']);
+    _assertSubtype(4, '$libP;D', 'Z', ['methodZ']);
+    _assertSubtype(5, '$libP;E', 'Z', ['methodZ']);
   }
 
   test_subtypes_classTypeAlias() async {
@@ -1052,19 +1045,16 @@
 class Y = A with B implements C, D;
 ''');
 
-    {
-      AnalysisDriverSubtype X =
-          index.subtypes.singleWhere((t) => t.name == 'X');
-      expect(X.supertypes, ['$libP;A', '$libP;B', '$libP;C']);
-      expect(X.members, isEmpty);
-    }
+    expect(index.supertypes, hasLength(7));
+    expect(index.subtypes, hasLength(7));
 
-    {
-      AnalysisDriverSubtype Y =
-          index.subtypes.singleWhere((t) => t.name == 'Y');
-      expect(Y.supertypes, ['$libP;A', '$libP;B', '$libP;C', '$libP;D']);
-      expect(Y.members, isEmpty);
-    }
+    _assertSubtype(0, '$libP;A', 'X', []);
+    _assertSubtype(1, '$libP;A', 'Y', []);
+    _assertSubtype(2, '$libP;B', 'X', []);
+    _assertSubtype(3, '$libP;B', 'Y', []);
+    _assertSubtype(4, '$libP;C', 'X', []);
+    _assertSubtype(5, '$libP;C', 'Y', []);
+    _assertSubtype(6, '$libP;D', 'Y', []);
   }
 
   test_subtypes_dynamic() async {
@@ -1074,9 +1064,8 @@
 }
 ''');
 
-    AnalysisDriverSubtype X = index.subtypes.singleWhere((t) => t.name == 'X');
-    expect(X.supertypes, isEmpty);
-    expect(X.members, ['foo']);
+    expect(index.supertypes, isEmpty);
+    expect(index.subtypes, isEmpty);
   }
 
   test_subtypes_mixinDeclaration() async {
@@ -1095,17 +1084,15 @@
 mixin Y on A, B implements C;
 ''');
 
-    {
-      var X = index.subtypes.singleWhere((t) => t.name == 'X');
-      expect(X.supertypes, ['$libP;A', '$libP;B', '$libP;C']);
-      expect(X.members, isEmpty);
-    }
+    expect(index.supertypes, hasLength(6));
+    expect(index.subtypes, hasLength(6));
 
-    {
-      var Y = index.subtypes.singleWhere((t) => t.name == 'Y');
-      expect(Y.supertypes, ['$libP;A', '$libP;B', '$libP;C']);
-      expect(Y.members, isEmpty);
-    }
+    _assertSubtype(0, '$libP;A', 'X', []);
+    _assertSubtype(1, '$libP;A', 'Y', []);
+    _assertSubtype(2, '$libP;B', 'X', []);
+    _assertSubtype(3, '$libP;B', 'Y', []);
+    _assertSubtype(4, '$libP;C', 'X', []);
+    _assertSubtype(5, '$libP;C', 'Y', []);
   }
 
   test_usedName_inLibraryIdentifier() async {
@@ -1213,6 +1200,14 @@
         'not found\n$element $expectedRelationKind at $expectedLocation');
   }
 
+  void _assertSubtype(
+      int i, String superEncoded, String subName, List<String> members) {
+    expect(index.strings[index.supertypes[i]], superEncoded);
+    var subtype = index.subtypes[i];
+    expect(index.strings[subtype.name], subName);
+    expect(_decodeStringList(subtype.members), members);
+  }
+
   void _assertUsedName(String name, IndexRelationKind kind,
       ExpectedLocation expectedLocation, bool isNot) {
     int nameId = _getStringId(name);
@@ -1233,6 +1228,10 @@
     _failWithIndexDump('Not found $name $kind at $expectedLocation');
   }
 
+  List<String> _decodeStringList(List<int> stringIds) {
+    return stringIds.map((i) => index.strings[i]).toList();
+  }
+
   ExpectedLocation _expectedLocation(String search, bool isQualified,
       {int length}) {
     int offset = findOffset(search);