Version 2.18.0-79.0.dev

Merge commit '017393ecdc690983bfec8facc258caccccb1e93d' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index ece08b5..8f93e61 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -340,9 +340,12 @@
         );
       });
 
-      final serverSuggestions = serverSuggestions2.map((serverSuggestion) {
-        return serverSuggestion.build();
-      }).toList();
+      final serverSuggestions =
+          performance.run('buildSuggestions', (performance) {
+        return serverSuggestions2
+            .map((serverSuggestion) => serverSuggestion.build())
+            .toList();
+      });
 
       final insertLength = _computeInsertLength(
         offset,
@@ -361,44 +364,46 @@
           ? false
           : server.clientConfiguration.global.completeFunctionCalls;
 
-      final results = serverSuggestions.map(
-        (item) {
-          var itemReplacementOffset =
-              item.replacementOffset ?? completionRequest.replacementOffset;
-          var itemReplacementLength =
-              item.replacementLength ?? completionRequest.replacementLength;
-          var itemInsertLength = insertLength;
+      final results = performance.run('mapSuggestions', (performance) {
+        return serverSuggestions.map(
+          (item) {
+            var itemReplacementOffset =
+                item.replacementOffset ?? completionRequest.replacementOffset;
+            var itemReplacementLength =
+                item.replacementLength ?? completionRequest.replacementLength;
+            var itemInsertLength = insertLength;
 
-          // Recompute the insert length if it may be affected by the above.
-          if (item.replacementOffset != null ||
-              item.replacementLength != null) {
-            itemInsertLength = _computeInsertLength(
-                offset, itemReplacementOffset, itemInsertLength);
-          }
+            // Recompute the insert length if it may be affected by the above.
+            if (item.replacementOffset != null ||
+                item.replacementLength != null) {
+              itemInsertLength = _computeInsertLength(
+                  offset, itemReplacementOffset, itemInsertLength);
+            }
 
-          // Convert to LSP ranges using the LineInfo.
-          Range? replacementRange = toRange(
-              unit.lineInfo, itemReplacementOffset, itemReplacementLength);
-          Range? insertionRange =
-              toRange(unit.lineInfo, itemReplacementOffset, itemInsertLength);
+            // Convert to LSP ranges using the LineInfo.
+            Range? replacementRange = toRange(
+                unit.lineInfo, itemReplacementOffset, itemReplacementLength);
+            Range? insertionRange =
+                toRange(unit.lineInfo, itemReplacementOffset, itemInsertLength);
 
-          return toCompletionItem(
-            capabilities,
-            unit.lineInfo,
-            item,
-            replacementRange: replacementRange,
-            insertionRange: insertionRange,
-            // TODO(dantup): Move commit characters to the main response
-            // and remove from each individual item (to reduce payload size)
-            // once the following change ships (and the Dart VS Code
-            // extension is updated to use it).
-            // https://github.com/microsoft/vscode-languageserver-node/issues/673
-            includeCommitCharacters:
-                server.clientConfiguration.global.previewCommitCharacters,
-            completeFunctionCalls: completeFunctionCalls,
-          );
-        },
-      ).toList();
+            return toCompletionItem(
+              capabilities,
+              unit.lineInfo,
+              item,
+              replacementRange: replacementRange,
+              insertionRange: insertionRange,
+              // TODO(dantup): Move commit characters to the main response
+              // and remove from each individual item (to reduce payload size)
+              // once the following change ships (and the Dart VS Code
+              // extension is updated to use it).
+              // https://github.com/microsoft/vscode-languageserver-node/issues/673
+              includeCommitCharacters:
+                  server.clientConfiguration.global.previewCommitCharacters,
+              completeFunctionCalls: completeFunctionCalls,
+            );
+          },
+        ).toList();
+      });
 
       // Now compute items in suggestion sets.
       var includedSuggestionSets = <IncludedSuggestionSet>[];
@@ -407,97 +412,107 @@
           includedElementKinds != null &&
           includedElementNames != null &&
           includedSuggestionRelevanceTags != null) {
-        computeIncludedSetList(
-          declarationsTracker,
-          completionRequest,
-          includedSuggestionSets,
-          includedElementNames,
-        );
+        performance.run('computeIncludedSetList', (performance) {
+          // Checked in `if` above.
+          includedElementNames!;
+
+          computeIncludedSetList(
+            declarationsTracker,
+            completionRequest,
+            includedSuggestionSets,
+            includedElementNames,
+          );
+        });
 
         // Build a fast lookup for imported symbols so that we can filter out
         // duplicates.
         final alreadyImportedSymbols = _buildLookupOfImportedSymbols(unit);
 
-        for (var includedSet in includedSuggestionSets) {
-          final library = declarationsTracker.getLibrary(includedSet.id);
-          if (library == null) {
-            break;
+        performance.run('addIncludedSuggestionSets', (performance) {
+          // Checked in `if` above.
+          includedSuggestionRelevanceTags!;
+
+          for (var includedSet in includedSuggestionSets) {
+            final library = declarationsTracker.getLibrary(includedSet.id);
+            if (library == null) {
+              break;
+            }
+
+            // Make a fast lookup for tag relevance.
+            final tagBoosts = <String, int>{};
+            for (var t in includedSuggestionRelevanceTags) {
+              tagBoosts[t.tag] = t.relevanceBoost;
+            }
+
+            // Only specific types of child declarations should be included.
+            // This list matches what's in _protocolAvailableSuggestion in
+            // the DAS implementation.
+            bool shouldIncludeChild(Declaration child) =>
+                child.kind == DeclarationKind.CONSTRUCTOR ||
+                child.kind == DeclarationKind.ENUM_CONSTANT ||
+                (child.kind == DeclarationKind.GETTER && child.isStatic) ||
+                (child.kind == DeclarationKind.FIELD && child.isStatic);
+
+            // Collect declarations and their children.
+            final allDeclarations = library.declarations
+                .followedBy(library.declarations
+                    .expand((decl) => decl.children.where(shouldIncludeChild)))
+                .toList();
+
+            final setResults = allDeclarations
+                // Filter to only the kinds we should return.
+                .where((item) => includedElementKinds!
+                    .contains(protocolElementKind(item.kind)))
+                .where((item) {
+              // Check existing imports to ensure we don't already import
+              // this element (this exact element from its declaring
+              // library, not just something with the same name). If we do
+              // we'll want to skip it.
+              final declaringUri =
+                  item.parent?.locationLibraryUri ?? item.locationLibraryUri!;
+
+              // For enums and named constructors, only the parent enum/class is in
+              // the list of imported symbols so we use the parents name.
+              final nameKey = item.kind == DeclarationKind.ENUM_CONSTANT ||
+                      item.kind == DeclarationKind.CONSTRUCTOR
+                  ? item.parent!.name
+                  : item.name;
+              final key = _createImportedSymbolKey(nameKey, declaringUri);
+              final importingUris = alreadyImportedSymbols[key];
+
+              // Keep it only if:
+              // - no existing imports include it
+              //     (in which case all libraries will be offered as
+              //     auto-imports)
+              // - this is the first imported URI that includes it
+              //     (we don't want to repeat it for each imported library that
+              //     includes it)
+              return importingUris == null ||
+                  importingUris.first == '${library.uri}';
+            }).map((item) => declarationToCompletionItem(
+                      capabilities,
+                      unit.path,
+                      offset,
+                      includedSet,
+                      library,
+                      tagBoosts,
+                      unit.lineInfo,
+                      item,
+                      completionRequest.replacementOffset,
+                      insertLength,
+                      completionRequest.replacementLength,
+                      // TODO(dantup): Move commit characters to the main response
+                      // and remove from each individual item (to reduce payload size)
+                      // once the following change ships (and the Dart VS Code
+                      // extension is updated to use it).
+                      // https://github.com/microsoft/vscode-languageserver-node/issues/673
+                      includeCommitCharacters: server
+                          .clientConfiguration.global.previewCommitCharacters,
+                      completeFunctionCalls: completeFunctionCalls,
+                    ));
+            results.addAll(setResults);
           }
-
-          // Make a fast lookup for tag relevance.
-          final tagBoosts = <String, int>{};
-          for (var t in includedSuggestionRelevanceTags) {
-            tagBoosts[t.tag] = t.relevanceBoost;
-          }
-
-          // Only specific types of child declarations should be included.
-          // This list matches what's in _protocolAvailableSuggestion in
-          // the DAS implementation.
-          bool shouldIncludeChild(Declaration child) =>
-              child.kind == DeclarationKind.CONSTRUCTOR ||
-              child.kind == DeclarationKind.ENUM_CONSTANT ||
-              (child.kind == DeclarationKind.GETTER && child.isStatic) ||
-              (child.kind == DeclarationKind.FIELD && child.isStatic);
-
-          // Collect declarations and their children.
-          final allDeclarations = library.declarations
-              .followedBy(library.declarations
-                  .expand((decl) => decl.children.where(shouldIncludeChild)))
-              .toList();
-
-          final setResults = allDeclarations
-              // Filter to only the kinds we should return.
-              .where((item) => includedElementKinds!
-                  .contains(protocolElementKind(item.kind)))
-              .where((item) {
-            // Check existing imports to ensure we don't already import
-            // this element (this exact element from its declaring
-            // library, not just something with the same name). If we do
-            // we'll want to skip it.
-            final declaringUri =
-                item.parent?.locationLibraryUri ?? item.locationLibraryUri!;
-
-            // For enums and named constructors, only the parent enum/class is in
-            // the list of imported symbols so we use the parents name.
-            final nameKey = item.kind == DeclarationKind.ENUM_CONSTANT ||
-                    item.kind == DeclarationKind.CONSTRUCTOR
-                ? item.parent!.name
-                : item.name;
-            final key = _createImportedSymbolKey(nameKey, declaringUri);
-            final importingUris = alreadyImportedSymbols[key];
-
-            // Keep it only if:
-            // - no existing imports include it
-            //     (in which case all libraries will be offered as
-            //     auto-imports)
-            // - this is the first imported URI that includes it
-            //     (we don't want to repeat it for each imported library that
-            //     includes it)
-            return importingUris == null ||
-                importingUris.first == '${library.uri}';
-          }).map((item) => declarationToCompletionItem(
-                    capabilities,
-                    unit.path,
-                    offset,
-                    includedSet,
-                    library,
-                    tagBoosts,
-                    unit.lineInfo,
-                    item,
-                    completionRequest.replacementOffset,
-                    insertLength,
-                    completionRequest.replacementLength,
-                    // TODO(dantup): Move commit characters to the main response
-                    // and remove from each individual item (to reduce payload size)
-                    // once the following change ships (and the Dart VS Code
-                    // extension is updated to use it).
-                    // https://github.com/microsoft/vscode-languageserver-node/issues/673
-                    includeCommitCharacters: server
-                        .clientConfiguration.global.previewCommitCharacters,
-                    completeFunctionCalls: completeFunctionCalls,
-                  ));
-          results.addAll(setResults);
-        }
+        });
       }
 
       // Add in any snippets.
@@ -510,23 +525,27 @@
       if (capabilities.completionSnippets &&
           snippetsEnabled &&
           isEditableFile) {
-        results.addAll(await _getDartSnippetItems(
-          clientCapabilities: capabilities,
-          unit: unit,
-          offset: offset,
-          lineInfo: unit.lineInfo,
-        ));
+        await performance.runAsync('addSnippets', (performance) async {
+          results.addAll(await _getDartSnippetItems(
+            clientCapabilities: capabilities,
+            unit: unit,
+            offset: offset,
+            lineInfo: unit.lineInfo,
+          ));
+        });
       }
 
       // Perform fuzzy matching based on the identifier in front of the caret to
       // reduce the size of the payload.
-      final fuzzyPattern = completionRequest.targetPrefix;
-      final fuzzyMatcher =
-          FuzzyMatcher(fuzzyPattern, matchStyle: MatchStyle.TEXT);
+      final matchingResults = performance.run('fuzzyFilter', (performance) {
+        final fuzzyPattern = completionRequest.targetPrefix;
+        final fuzzyMatcher =
+            FuzzyMatcher(fuzzyPattern, matchStyle: MatchStyle.TEXT);
 
-      final matchingResults = results
-          .where((e) => fuzzyMatcher.score(e.filterText ?? e.label) > 0)
-          .toList();
+        return results
+            .where((e) => fuzzyMatcher.score(e.filterText ?? e.label) > 0)
+            .toList();
+      });
 
       // Transmitted count will be set after combining with plugins.
       completionPerformance.computedSuggestionCount = matchingResults.length;
diff --git a/pkg/analysis_server/lib/src/services/completion/completion_performance.dart b/pkg/analysis_server/lib/src/services/completion/completion_performance.dart
index 069d5df..0988854 100644
--- a/pkg/analysis_server/lib/src/services/completion/completion_performance.dart
+++ b/pkg/analysis_server/lib/src/services/completion/completion_performance.dart
@@ -35,6 +35,8 @@
 
 /// Overall performance of a code completion operation.
 class CompletionPerformance {
+  static var _nextId = 1;
+  final int id;
   final OperationPerformance operation;
   final String path;
   final String snippet;
@@ -48,7 +50,8 @@
     this.requestLatency,
     required String content,
     required int offset,
-  }) : snippet = _computeCompletionSnippet(content, offset);
+  })  : id = _nextId++,
+        snippet = _computeCompletionSnippet(content, offset);
 
   String get computedSuggestionCountStr {
     if (computedSuggestionCount == null) return '';
diff --git a/pkg/analysis_server/lib/src/status/diagnostics.dart b/pkg/analysis_server/lib/src/status/diagnostics.dart
index 148f1db..b4d5a3a 100644
--- a/pkg/analysis_server/lib/src/status/diagnostics.dart
+++ b/pkg/analysis_server/lib/src/status/diagnostics.dart
@@ -138,6 +138,10 @@
 .footer strong {
   color: #333;
 }
+
+.subtle {
+  color: #333;
+}
 ''';
 
 String get _sdkVersion {
@@ -220,7 +224,9 @@
     for (var completion in completions) {
       var shortName = pathContext.basename(completion.path);
       buf.writeln('<tr>'
-          '<td class="pre right">${_formatTiming(completion)}</td>'
+          '<td class="pre right"><a href="/timing?id=${completion.id}">'
+          '${_formatTiming(completion)}'
+          '</a></td>'
           '<td class="right">${completion.computedSuggestionCountStr}</td>'
           '<td class="right">${completion.transmittedSuggestionCountStr}</td>'
           '<td>${escape(shortName)}</td>'
@@ -237,7 +243,7 @@
     var latency = completion.requestLatency;
     if (latency != null) {
       buffer
-        ..write(' <small title="client-to-server latency">(+ ')
+        ..write(' <small class="subtle" title="client-to-server latency">(+ ')
         ..write(printMilliseconds(latency))
         ..write(')</small>');
     }
@@ -246,6 +252,37 @@
   }
 }
 
+abstract class AbstractCompletionTimingPage extends DiagnosticPageWithNav {
+  AbstractCompletionTimingPage(DiagnosticsSite site)
+      : super(site, 'timing', 'Timing', description: 'Timing statistics.');
+
+  path.Context get pathContext;
+
+  List<CompletionPerformance> get performanceItems;
+
+  @override
+  Future generateContent(Map<String, String> params) async {
+    var id = int.parse(params['id'] ?? '');
+    var completionInfo =
+        performanceItems.firstWhereOrNull((info) => info.id == id);
+
+    if (completionInfo == null) {
+      blankslate('Unable to find completion data for $id. '
+          'Perhaps newer completion requests have pushed it out of the buffer?');
+      return;
+    }
+
+    var buffer = StringBuffer();
+    completionInfo.operation.write(buffer: buffer);
+    pre(() {
+      buf.write('<code>');
+      buf.write(escape('$buffer'));
+      buf.writeln('</code>');
+    });
+    return;
+  }
+}
+
 class AstPage extends DiagnosticPageWithNav {
   String? _description;
 
@@ -390,6 +427,20 @@
       server.completionState.performanceList.items.toList();
 }
 
+class CompletionTimingPage extends AbstractCompletionTimingPage {
+  @override
+  AnalysisServer server;
+
+  CompletionTimingPage(super.site, this.server);
+
+  @override
+  path.Context get pathContext => server.resourceProvider.pathContext;
+
+  @override
+  List<CompletionPerformance> get performanceItems =>
+      server.completionState.performanceList.items.toList();
+}
+
 class ContentsPage extends DiagnosticPageWithNav {
   String? _description;
 
@@ -792,9 +843,11 @@
     }
     if (server is AnalysisServer) {
       pages.add(CompletionPage(this, server));
+      pages.add(CompletionTimingPage(this, server));
       pages.add(SubscriptionsPage(this, server));
     } else if (server is LspAnalysisServer) {
       pages.add(LspCompletionPage(this, server));
+      pages.add(LspCompletionTimingPage(this, server));
       pages.add(LspCapabilitiesPage(this, server));
     }
 
@@ -1068,6 +1121,20 @@
       server.performanceStats.completion.items.toList();
 }
 
+class LspCompletionTimingPage extends AbstractCompletionTimingPage {
+  @override
+  LspAnalysisServer server;
+
+  LspCompletionTimingPage(super.site, this.server);
+
+  @override
+  path.Context get pathContext => server.resourceProvider.pathContext;
+
+  @override
+  List<CompletionPerformance> get performanceItems =>
+      server.performanceStats.completion.items.toList();
+}
+
 class MemoryAndCpuPage extends DiagnosticPageWithNav {
   final ProcessProfiler profiler;
 
diff --git a/pkg/compiler/lib/src/serialization/member_data.dart b/pkg/compiler/lib/src/serialization/member_data.dart
index 7e780fd..00c74bb 100644
--- a/pkg/compiler/lib/src/serialization/member_data.dart
+++ b/pkg/compiler/lib/src/serialization/member_data.dart
@@ -2,41 +2,41 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.10
+import 'package:kernel/ast.dart' as ir;
 
-part of 'serialization.dart';
+import 'node_indexer.dart';
 
 /// Helper for looking up object library data from an [ir.Component] node.
 class ComponentLookup {
   final ir.Component _component;
 
-  /// Cache of [_LibraryData] for libraries in [_component].
-  Map<Uri, _LibraryData> _libraryMap;
+  /// Cache of [LibraryData] for libraries in [_component].
+  late final Map<Uri, LibraryData> _libraryMap = _initializeLibraryMap();
 
   ComponentLookup(this._component);
 
-  /// Returns the [_LibraryData] object for the library with the [canonicalUri].
-  _LibraryData getLibraryDataByUri(Uri canonicalUri) {
-    if (_libraryMap == null) {
-      _libraryMap = {};
-      for (ir.Library library in _component.libraries) {
-        _libraryMap[library.importUri] = _LibraryData(library);
-      }
+  Map<Uri, LibraryData> _initializeLibraryMap() {
+    final libraryMap = <Uri, LibraryData>{};
+    for (ir.Library library in _component.libraries) {
+      libraryMap[library.importUri] = LibraryData(library);
     }
-    _LibraryData data = _libraryMap[canonicalUri];
-    assert(data != null, "No library found for $canonicalUri.");
-    return data;
+    return libraryMap;
+  }
+
+  /// Returns the [LibraryData] object for the library with the [canonicalUri].
+  LibraryData getLibraryDataByUri(Uri canonicalUri) {
+    return _libraryMap[canonicalUri]!;
   }
 }
 
 /// Returns a name uniquely identifying a member within its enclosing library
 /// or class.
-String _computeMemberName(ir.Member member) {
+String computeMemberName(ir.Member member) {
   // This should mostly be empty except when serializing the name of nSM
   // forwarders (see dartbug.com/33732).
   String libraryPrefix = member.name.isPrivate &&
           member.name.libraryName != member.enclosingLibrary.reference
-      ? '${member.name.libraryName.canonicalName.name}:'
+      ? '${member.name.libraryName?.canonicalName?.name}:'
       : '';
   String name = member.name.text;
   if (member is ir.Constructor) {
@@ -52,196 +52,195 @@
 }
 
 /// Helper for looking up classes and members from an [ir.Library] node.
-class _LibraryData {
+class LibraryData {
   /// The [ir.Library] that defines the library.
   final ir.Library node;
 
-  /// Cache of [_ClassData] for classes in this library.
-  Map<String, _ClassData> _classesByName;
-  Map<ir.Class, _ClassData> _classesByNode;
+  /// Cache of [ClassData] for classes in this library.
+  Map<String, ClassData>? _classesByName;
+  Map<ir.Class, ClassData>? _classesByNode;
 
   /// Cache of [ir.Typedef] nodes for typedefs in this library.
-  Map<String, ir.Typedef> _typedefs;
+  late final Map<String, ir.Typedef> _typedefs = _initializeTypedefs();
 
-  /// Cache of [_MemberData] for members in this library.
-  Map<String, _MemberData> _membersByName;
-  Map<ir.Member, _MemberData> _membersByNode;
+  /// Cache of [MemberData] for members in this library.
+  Map<String, MemberData>? _membersByName;
+  Map<ir.Member, MemberData>? _membersByNode;
 
-  _LibraryData(this.node);
+  LibraryData(this.node);
+
+  Map<String, ir.Typedef> _initializeTypedefs() {
+    final typedefs = <String, ir.Typedef>{};
+    for (ir.Typedef typedef in node.typedefs) {
+      assert(
+          !typedefs.containsKey(typedef.name),
+          "Duplicate typedef '${typedef.name}' in $typedefs "
+          "trying to add $typedef.");
+      typedefs[typedef.name] = typedef;
+    }
+    return typedefs;
+  }
 
   void _ensureClasses() {
     if (_classesByName == null) {
-      _classesByName = {};
-      _classesByNode = {};
+      final classesByName = _classesByName = {};
+      final classesByNode = _classesByNode = {};
       for (ir.Class cls in node.classes) {
         assert(
-            !_classesByName.containsKey(cls.name),
-            "Duplicate class '${cls.name}' in $_classesByName "
+            !classesByName.containsKey(cls.name),
+            "Duplicate class '${cls.name}' in $classesByName "
             "trying to add $cls.");
         assert(
-            !_classesByNode.containsKey(cls),
-            "Duplicate class '${cls.name}' in $_classesByNode "
+            !classesByNode.containsKey(cls),
+            "Duplicate class '${cls.name}' in $classesByNode "
             "trying to add $cls.");
-        _classesByNode[cls] = _classesByName[cls.name] = _ClassData(cls);
+        classesByNode[cls] = classesByName[cls.name] = ClassData(cls);
       }
     }
   }
 
-  /// Returns the [_ClassData] for the class [name] in this library.
-  _ClassData lookupClassByName(String name) {
+  /// Returns the [ClassData] for the class [name] in this library.
+  ClassData? lookupClassByName(String name) {
     _ensureClasses();
-    return _classesByName[name];
+    return _classesByName![name];
   }
 
-  /// Returns the [_ClassData] for the class [node] in this library.
-  _ClassData lookupClassByNode(ir.Class node) {
+  /// Returns the [ClassData] for the class [node] in this library.
+  ClassData? lookupClassByNode(ir.Class node) {
     _ensureClasses();
-    return _classesByNode[node];
+    return _classesByNode![node];
   }
 
-  ir.Typedef lookupTypedef(String name) {
-    if (_typedefs == null) {
-      _typedefs = {};
-      for (ir.Typedef typedef in node.typedefs) {
-        assert(
-            !_typedefs.containsKey(typedef.name),
-            "Duplicate typedef '${typedef.name}' in $_typedefs "
-            "trying to add $typedef.");
-        _typedefs[typedef.name] = typedef;
-      }
-    }
+  ir.Typedef? lookupTypedef(String name) {
     return _typedefs[name];
   }
 
   void _ensureMembers() {
     if (_membersByName == null) {
-      _membersByName = {};
-      _membersByNode = {};
+      final membersByName = _membersByName = {};
+      final membersByNode = _membersByNode = {};
       for (ir.Member member in node.members) {
-        String name = _computeMemberName(member);
-        if (name == null) continue;
+        String name = computeMemberName(member);
         assert(
-            !_membersByName.containsKey(name),
-            "Duplicate member '$name' in $_membersByName "
+            !membersByName.containsKey(name),
+            "Duplicate member '$name' in $membersByName "
             "trying to add $member.");
         assert(
-            !_membersByNode.containsKey(member),
-            "Duplicate member '$name' in $_membersByNode "
+            !membersByNode.containsKey(member),
+            "Duplicate member '$name' in $membersByNode "
             "trying to add $member.");
-        _membersByNode[member] = _membersByName[name] = _MemberData(member);
+        membersByNode[member] = membersByName[name] = MemberData(member);
       }
     }
   }
 
-  /// Returns the [_MemberData] for the member uniquely identified by [name] in
+  /// Returns the [MemberData] for the member uniquely identified by [name] in
   /// this library.
-  _MemberData lookupMemberDataByName(String name) {
+  MemberData? lookupMemberDataByName(String name) {
     _ensureMembers();
-    return _membersByName[name];
+    return _membersByName![name];
   }
 
-  /// Returns the [_MemberData] for the member [node] in this library.
-  _MemberData lookupMemberDataByNode(ir.Member node) {
+  /// Returns the [MemberData] for the member [node] in this library.
+  MemberData? lookupMemberDataByNode(ir.Member node) {
     _ensureMembers();
-    return _membersByNode[node];
+    return _membersByNode![node];
   }
 
   @override
-  String toString() => '_LibraryData($node(${identityHashCode(node)}))';
+  String toString() => 'LibraryData($node(${identityHashCode(node)}))';
 }
 
 /// Helper for looking up members from an [ir.Class] node.
-class _ClassData {
+class ClassData {
   /// The [ir.Class] that defines the class.
   final ir.Class node;
 
-  /// Cache of [_MemberData] for members in this class.
-  Map<String, _MemberData> _membersByName;
-  Map<ir.Member, _MemberData> _membersByNode;
+  /// Cache of [MemberData] for members in this class.
+  Map<String, MemberData>? _membersByName;
+  Map<ir.Member, MemberData>? _membersByNode;
 
-  _ClassData(this.node);
+  ClassData(this.node);
 
   void _ensureMembers() {
     if (_membersByName == null) {
-      _membersByName = {};
-      _membersByNode = {};
+      final membersByName = _membersByName = {};
+      final membersByNode = _membersByNode = {};
       for (ir.Member member in node.members) {
-        String name = _computeMemberName(member);
-        if (name == null) continue;
+        String name = computeMemberName(member);
         assert(
-            !_membersByName.containsKey(name),
-            "Duplicate member '$name' in $_membersByName "
+            !membersByName.containsKey(name),
+            "Duplicate member '$name' in $membersByName "
             "trying to add $member.");
         assert(
-            !_membersByNode.containsKey(member),
-            "Duplicate member '$name' in $_membersByNode "
+            !membersByNode.containsKey(member),
+            "Duplicate member '$name' in $membersByNode "
             "trying to add $member.");
-        _membersByNode[member] = _membersByName[name] = _MemberData(member);
+        membersByNode[member] = membersByName[name] = MemberData(member);
       }
     }
   }
 
-  /// Returns the [_MemberData] for the member uniquely identified by [name] in
+  /// Returns the [MemberData] for the member uniquely identified by [name] in
   /// this class.
-  _MemberData lookupMemberDataByName(String name) {
+  MemberData? lookupMemberDataByName(String name) {
     _ensureMembers();
-    return _membersByName[name];
+    return _membersByName![name];
   }
 
-  /// Returns the [_MemberData] for the member [node] in this class.
-  _MemberData lookupMemberDataByNode(ir.Member node) {
+  /// Returns the [MemberData] for the member [node] in this class.
+  MemberData? lookupMemberDataByNode(ir.Member node) {
     _ensureMembers();
-    return _membersByNode[node];
+    return _membersByNode![node];
   }
 
   @override
-  String toString() => '_ClassData($node(${identityHashCode(node)}))';
+  String toString() => 'ClassData($node(${identityHashCode(node)}))';
 }
 
 /// Helper for looking up child [ir.TreeNode]s of a [ir.Member] node.
-class _MemberData {
+class MemberData {
   /// The [ir.Member] that defines the member.
   final ir.Member node;
 
   /// Cached index to [ir.TreeNode] map used for deserialization of
   /// [ir.TreeNode]s.
-  Map<int, ir.TreeNode> _indexToNodeMap;
+  Map<int, ir.TreeNode>? _indexToNodeMap;
 
   /// Cached [ir.TreeNode] to index map used for serialization of
   /// [ir.TreeNode]s.
-  Map<ir.TreeNode, int> _nodeToIndexMap;
+  Map<ir.TreeNode, int>? _nodeToIndexMap;
 
-  /// Cached [ir.ConstantExpression] to [_ConstantNodeIndexerVisitor] map used
+  /// Cached [ir.ConstantExpression] to [ConstantNodeIndexerVisitor] map used
   /// for fast serialization/deserialization of constant references.
-  Map<ir.ConstantExpression, _ConstantNodeIndexerVisitor> _constantIndexMap;
+  late final Map<ir.ConstantExpression, ConstantNodeIndexerVisitor>
+      _constantIndexMap = {};
 
-  _MemberData(this.node);
+  MemberData(this.node);
 
   void _ensureMaps() {
     if (_indexToNodeMap == null) {
       _indexToNodeMap = {};
       _nodeToIndexMap = {};
-      node.accept(_TreeNodeIndexerVisitor(_indexToNodeMap, _nodeToIndexMap));
+      node.accept(TreeNodeIndexerVisitor(_indexToNodeMap!, _nodeToIndexMap!));
     }
   }
 
-  _ConstantNodeIndexerVisitor _createConstantIndexer(
+  ConstantNodeIndexerVisitor _createConstantIndexer(
       ir.ConstantExpression node) {
-    _ConstantNodeIndexerVisitor indexer = _ConstantNodeIndexerVisitor();
+    ConstantNodeIndexerVisitor indexer = ConstantNodeIndexerVisitor();
     node.constant.accept(indexer);
     return indexer;
   }
 
   ir.Constant getConstantByIndex(ir.ConstantExpression node, int index) {
-    _constantIndexMap ??= {};
-    _ConstantNodeIndexerVisitor indexer =
+    ConstantNodeIndexerVisitor indexer =
         _constantIndexMap[node] ??= _createConstantIndexer(node);
     return indexer.getConstant(index);
   }
 
   int getIndexByConstant(ir.ConstantExpression node, ir.Constant constant) {
-    _constantIndexMap ??= {};
-    _ConstantNodeIndexerVisitor indexer =
+    ConstantNodeIndexerVisitor indexer =
         _constantIndexMap[node] ??= _createConstantIndexer(node);
     return indexer.getIndex(constant);
   }
@@ -249,19 +248,15 @@
   /// Returns the [ir.TreeNode] corresponding to [index] in this member.
   ir.TreeNode getTreeNodeByIndex(int index) {
     _ensureMaps();
-    ir.TreeNode treeNode = _indexToNodeMap[index];
-    assert(treeNode != null, "No TreeNode found for index $index in $node.");
-    return treeNode;
+    return _indexToNodeMap![index]!;
   }
 
   /// Returns the index corresponding to [ir.TreeNode] in this member.
   int getIndexByTreeNode(ir.TreeNode node) {
     _ensureMaps();
-    int index = _nodeToIndexMap[node];
-    assert(index != null, "No index found for ${node.runtimeType}.");
-    return index;
+    return _nodeToIndexMap![node]!;
   }
 
   @override
-  String toString() => '_MemberData($node(${identityHashCode(node)}))';
+  String toString() => 'MemberData($node(${identityHashCode(node)}))';
 }
diff --git a/pkg/compiler/lib/src/serialization/node_indexer.dart b/pkg/compiler/lib/src/serialization/node_indexer.dart
index a61c244..8a7459c 100644
--- a/pkg/compiler/lib/src/serialization/node_indexer.dart
+++ b/pkg/compiler/lib/src/serialization/node_indexer.dart
@@ -2,19 +2,16 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.10
-
-part of 'serialization.dart';
+import 'package:kernel/ast.dart' as ir;
 
 /// Visitor that ascribes an index to all [ir.TreeNode]s that potentially
 /// needed for serialization and deserialization.
-class _TreeNodeIndexerVisitor extends ir.Visitor<void>
-    with ir.VisitorVoidMixin {
+class TreeNodeIndexerVisitor extends ir.Visitor<void> with ir.VisitorVoidMixin {
   int _currentIndex = 0;
   final Map<int, ir.TreeNode> _indexToNodeMap;
   final Map<ir.TreeNode, int> _nodeToIndexMap;
 
-  _TreeNodeIndexerVisitor(this._indexToNodeMap, this._nodeToIndexMap);
+  TreeNodeIndexerVisitor(this._indexToNodeMap, this._nodeToIndexMap);
 
   void registerNode(ir.TreeNode node) {
     _indexToNodeMap[_currentIndex] = node;
@@ -254,14 +251,14 @@
 
 /// Visitor that ascribes an index to all [ir.Constant]s that we potentially
 /// need to reference for serialization and deserialization.
-class _ConstantNodeIndexerVisitor implements ir.ConstantVisitor<void> {
+class ConstantNodeIndexerVisitor implements ir.ConstantVisitor<void> {
   int _currentIndex = 0;
   final Map<int, ir.Constant> _indexToNodeMap = {};
   final Map<ir.Constant, int> _nodeToIndexMap = {};
 
   /// Returns `true` if node not already registered.
   bool _register(ir.Constant node) {
-    int index = _nodeToIndexMap[node];
+    int? index = _nodeToIndexMap[node];
     if (index != null) return false;
     _indexToNodeMap[_currentIndex] = node;
     _nodeToIndexMap[node] = _currentIndex;
@@ -271,13 +268,13 @@
 
   int getIndex(ir.Constant node) {
     assert(_nodeToIndexMap.containsKey(node), "Constant without index: $node");
-    return _nodeToIndexMap[node];
+    return _nodeToIndexMap[node]!;
   }
 
   ir.Constant getConstant(int index) {
     assert(
         _indexToNodeMap.containsKey(index), "Index without constant: $index");
-    return _indexToNodeMap[index];
+    return _indexToNodeMap[index]!;
   }
 
   @override
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
index 2cf3679..af54708 100644
--- a/pkg/compiler/lib/src/serialization/serialization.dart
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -25,6 +25,8 @@
 import '../js_model/locals.dart';
 import '../js_model/type_recipe.dart' show TypeRecipe;
 
+import 'member_data.dart';
+export 'member_data.dart' show ComponentLookup, computeMemberName;
 import 'serialization_interfaces.dart' as migrated
     show DataSourceReader, DataSinkWriter;
 import 'tags.dart';
@@ -35,8 +37,6 @@
 part 'binary_sink.dart';
 part 'binary_source.dart';
 part 'helpers.dart';
-part 'member_data.dart';
-part 'node_indexer.dart';
 part 'object_sink.dart';
 part 'object_source.dart';
 
diff --git a/pkg/compiler/lib/src/serialization/sink.dart b/pkg/compiler/lib/src/serialization/sink.dart
index a3e37af..ca1eddd 100644
--- a/pkg/compiler/lib/src/serialization/sink.dart
+++ b/pkg/compiler/lib/src/serialization/sink.dart
@@ -54,8 +54,8 @@
   /// inconsistencies between serialization and deserialization.
   List<String> _tags;
 
-  /// Map of [_MemberData] object for serialized kernel member nodes.
-  final Map<ir.Member, _MemberData> _memberData = {};
+  /// Map of [MemberData] object for serialized kernel member nodes.
+  final Map<ir.Member, MemberData> _memberData = {};
 
   IndexedSink<String> _stringIndex;
   IndexedSink<Uri> _uriIndex;
@@ -71,7 +71,7 @@
   final Map<String, int> tagFrequencyMap;
 
   ir.Member _currentMemberContext;
-  _MemberData _currentMemberData;
+  MemberData _currentMemberData;
 
   IndexedSink<T> _createSink<T>() {
     if (importedIndices == null || !importedIndices.caches.containsKey(T)) {
@@ -340,11 +340,11 @@
     if (cls != null) {
       _sinkWriter.writeEnum(MemberContextKind.cls);
       _writeClassNode(cls);
-      _writeString(_computeMemberName(value));
+      _writeString(computeMemberName(value));
     } else {
       _sinkWriter.writeEnum(MemberContextKind.library);
       _writeLibraryNode(value.enclosingLibrary);
-      _writeString(_computeMemberName(value));
+      _writeString(computeMemberName(value));
     }
   }
 
@@ -410,7 +410,7 @@
     _writeTreeNode(value, null);
   }
 
-  void _writeTreeNode(ir.TreeNode value, _MemberData memberData) {
+  void _writeTreeNode(ir.TreeNode value, MemberData memberData) {
     if (value is ir.Class) {
       _sinkWriter.writeEnum(_TreeNodeKind.cls);
       _writeClassNode(value);
@@ -502,7 +502,7 @@
   }
 
   void writeTreeNodeInContextInternal(
-      ir.TreeNode value, _MemberData memberData) {
+      ir.TreeNode value, MemberData memberData) {
     _writeDataKind(DataKind.treeNode);
     _writeTreeNode(value, memberData);
   }
@@ -567,7 +567,7 @@
     _writeTypeParameter(value, null);
   }
 
-  void _writeTypeParameter(ir.TypeParameter value, _MemberData memberData) {
+  void _writeTypeParameter(ir.TypeParameter value, MemberData memberData) {
     ir.TreeNode parent = value.parent;
     if (parent is ir.Class) {
       _sinkWriter.writeEnum(_TypeParameterKind.cls);
@@ -1243,7 +1243,7 @@
   @override
   void inMemberContext(ir.Member context, void f()) {
     ir.Member oldMemberContext = _currentMemberContext;
-    _MemberData oldMemberData = _currentMemberData;
+    MemberData oldMemberData = _currentMemberData;
     _currentMemberContext = context;
     _currentMemberData = null;
     f();
@@ -1251,14 +1251,14 @@
     _currentMemberContext = oldMemberContext;
   }
 
-  _MemberData get currentMemberData {
+  MemberData get currentMemberData {
     assert(_currentMemberContext != null,
         "DataSink has no current member context.");
     return _currentMemberData ??= _memberData[_currentMemberContext] ??=
-        _MemberData(_currentMemberContext);
+        MemberData(_currentMemberContext);
   }
 
-  _MemberData _getMemberData(ir.TreeNode node) {
+  MemberData _getMemberData(ir.TreeNode node) {
     ir.TreeNode member = node;
     while (member is! ir.Member) {
       if (member == null) {
@@ -1268,10 +1268,10 @@
       member = member.parent;
     }
     _writeMemberNode(member);
-    return _memberData[member] ??= _MemberData(member);
+    return _memberData[member] ??= MemberData(member);
   }
 
-  void _writeFunctionNode(ir.FunctionNode value, _MemberData memberData) {
+  void _writeFunctionNode(ir.FunctionNode value, MemberData memberData) {
     ir.TreeNode parent = value.parent;
     if (parent is ir.Procedure) {
       _sinkWriter.writeEnum(_FunctionNodeKind.procedure);
diff --git a/pkg/compiler/lib/src/serialization/source.dart b/pkg/compiler/lib/src/serialization/source.dart
index df62ec5..b5c8689 100644
--- a/pkg/compiler/lib/src/serialization/source.dart
+++ b/pkg/compiler/lib/src/serialization/source.dart
@@ -53,14 +53,14 @@
 
   IndexedSource<String> _stringIndex;
   IndexedSource<Uri> _uriIndex;
-  IndexedSource<_MemberData> _memberNodeIndex;
+  IndexedSource<MemberData> _memberNodeIndex;
   IndexedSource<ImportEntity> _importIndex;
   IndexedSource<ConstantValue> _constantIndex;
 
   final Map<Type, IndexedSource> _generalCaches = {};
 
   ir.Member _currentMemberContext;
-  _MemberData _currentMemberData;
+  MemberData _currentMemberData;
 
   IndexedSource<T> _createSource<T>() {
     if (importedIndices == null || !importedIndices.caches.containsKey(T)) {
@@ -75,7 +75,7 @@
       {this.useDataKinds = false, this.importedIndices, this.interner}) {
     _stringIndex = _createSource<String>();
     _uriIndex = _createSource<Uri>();
-    _memberNodeIndex = _createSource<_MemberData>();
+    _memberNodeIndex = _createSource<MemberData>();
     _importIndex = _createSource<ImportEntity>();
     _constantIndex = _createSource<ConstantValue>();
   }
@@ -89,9 +89,9 @@
     indices.caches[ImportEntity] = DataSourceTypeIndices(_importIndex.cache);
     // _memberNodeIndex needs two entries depending on if the indices will be
     // consumed by a [DataSource] or [DataSink].
-    indices.caches[_MemberData] = DataSourceTypeIndices(_memberNodeIndex.cache);
-    indices.caches[ir.Member] = DataSourceTypeIndices<ir.Member, _MemberData>(
-        _memberNodeIndex.cache, (_MemberData data) => data?.node);
+    indices.caches[MemberData] = DataSourceTypeIndices(_memberNodeIndex.cache);
+    indices.caches[ir.Member] = DataSourceTypeIndices<ir.Member, MemberData>(
+        _memberNodeIndex.cache, (MemberData data) => data?.node);
     indices.caches[ConstantValue] = DataSourceTypeIndices(_constantIndex.cache);
     _generalCaches.forEach((type, indexedSource) {
       indices.caches[type] = DataSourceTypeIndices(indexedSource.cache);
@@ -180,7 +180,7 @@
   @override
   T inMemberContext<T>(ir.Member context, T f()) {
     ir.Member oldMemberContext = _currentMemberContext;
-    _MemberData oldMemberData = _currentMemberData;
+    MemberData oldMemberData = _currentMemberData;
     _currentMemberContext = context;
     _currentMemberData = null;
     T result = f();
@@ -189,7 +189,7 @@
     return result;
   }
 
-  _MemberData get currentMemberData {
+  MemberData get currentMemberData {
     assert(_currentMemberContext != null,
         "DataSink has no current member context.");
     return _currentMemberData ??= _getMemberData(_currentMemberContext);
@@ -359,7 +359,7 @@
     return _readLibraryData().node;
   }
 
-  _LibraryData _readLibraryData() {
+  LibraryData _readLibraryData() {
     Uri canonicalUri = _readUri();
     return componentLookup.getLibraryDataByUri(canonicalUri);
   }
@@ -370,8 +370,8 @@
     return _readClassData().node;
   }
 
-  _ClassData _readClassData() {
-    _LibraryData library = _readLibraryData();
+  ClassData _readClassData() {
+    LibraryData library = _readLibraryData();
     String name = _readString();
     return library.lookupClassByName(name);
   }
@@ -383,7 +383,7 @@
   }
 
   ir.Typedef _readTypedefNode() {
-    _LibraryData library = _readLibraryData();
+    LibraryData library = _readLibraryData();
     String name = _readString();
     return library.lookupTypedef(name);
   }
@@ -394,19 +394,19 @@
     return _readMemberData().node;
   }
 
-  _MemberData _readMemberData() {
+  MemberData _readMemberData() {
     return _memberNodeIndex.read(_readMemberDataInternal);
   }
 
-  _MemberData _readMemberDataInternal() {
+  MemberData _readMemberDataInternal() {
     MemberContextKind kind = _sourceReader.readEnum(MemberContextKind.values);
     switch (kind) {
       case MemberContextKind.cls:
-        _ClassData cls = _readClassData();
+        ClassData cls = _readClassData();
         String name = _readString();
         return cls.lookupMemberDataByName(name);
       case MemberContextKind.library:
-        _LibraryData library = _readLibraryData();
+        LibraryData library = _readLibraryData();
         String name = _readString();
         return library.lookupMemberDataByName(name);
     }
@@ -474,7 +474,7 @@
     return _readTreeNode(null);
   }
 
-  ir.TreeNode _readTreeNode(_MemberData memberData) {
+  ir.TreeNode _readTreeNode(MemberData memberData) {
     _TreeNodeKind kind = _sourceReader.readEnum(_TreeNodeKind.values);
     switch (kind) {
       case _TreeNodeKind.cls:
@@ -558,7 +558,7 @@
     return readTreeNodeInContextInternal(currentMemberData);
   }
 
-  ir.TreeNode readTreeNodeInContextInternal(_MemberData memberData) {
+  ir.TreeNode readTreeNodeInContextInternal(MemberData memberData) {
     _checkDataKind(DataKind.treeNode);
     return _readTreeNode(memberData);
   }
@@ -628,7 +628,7 @@
     return _readTypeParameter(null);
   }
 
-  ir.TypeParameter _readTypeParameter(_MemberData memberData) {
+  ir.TypeParameter _readTypeParameter(MemberData memberData) {
     _TypeParameterKind kind = _sourceReader.readEnum(_TypeParameterKind.values);
     switch (kind) {
       case _TypeParameterKind.cls:
@@ -1354,18 +1354,18 @@
     return _codegenReader.readTypeRecipe(this);
   }
 
-  _MemberData _getMemberData(ir.Member node) {
-    _LibraryData libraryData =
+  MemberData _getMemberData(ir.Member node) {
+    LibraryData libraryData =
         componentLookup.getLibraryDataByUri(node.enclosingLibrary.importUri);
     if (node.enclosingClass != null) {
-      _ClassData classData = libraryData.lookupClassByNode(node.enclosingClass);
+      ClassData classData = libraryData.lookupClassByNode(node.enclosingClass);
       return classData.lookupMemberDataByNode(node);
     } else {
       return libraryData.lookupMemberDataByNode(node);
     }
   }
 
-  ir.FunctionNode _readFunctionNode(_MemberData memberData) {
+  ir.FunctionNode _readFunctionNode(MemberData memberData) {
     _FunctionNodeKind kind = _sourceReader.readEnum(_FunctionNodeKind.values);
     switch (kind) {
       case _FunctionNodeKind.procedure:
diff --git a/sdk/bin/dart2js b/sdk/bin/dart2js
index 750f20d..e038b1a 100755
--- a/sdk/bin/dart2js
+++ b/sdk/bin/dart2js
@@ -53,4 +53,4 @@
 
 DART2JS="package:compiler/src/dart2js.dart"
 
-exec "$DART" "--packages=$DART_ROOT/.packages" "${EXTRA_VM_OPTIONS[@]}" "$DART2JS" "${EXTRA_OPTIONS[@]}" "$@"
+exec "$DART" "--packages=$DART_ROOT/.dart_tool/package_config.json" "${EXTRA_VM_OPTIONS[@]}" "$DART2JS" "${EXTRA_OPTIONS[@]}" "$@"
diff --git a/sdk/lib/_internal/wasm/lib/string_patch.dart b/sdk/lib/_internal/wasm/lib/string_patch.dart
index a9c0222..5eb2461 100644
--- a/sdk/lib/_internal/wasm/lib/string_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/string_patch.dart
@@ -389,10 +389,10 @@
     if ((startIndex + 1) == endIndex) {
       return this[startIndex];
     }
-    return _substringUncheckedNative(startIndex, endIndex);
+    return _substringUncheckedInternal(startIndex, endIndex);
   }
 
-  external String _substringUncheckedNative(int startIndex, int endIndex);
+  String _substringUncheckedInternal(int startIndex, int endIndex);
 
   // Checks for one-byte whitespaces only.
   static bool _isOneByteWhitespace(int codeUnit) {
@@ -929,6 +929,7 @@
         super._();
 
   // Same hash as VM
+  @override
   int _computeHashCode() {
     WasmIntArray<WasmI8> array = _array;
     int length = array.length;
@@ -939,10 +940,13 @@
     return _StringBase._finalizeHash(hash);
   }
 
+  @override
   int codeUnitAt(int index) => _array.readUnsigned(index);
 
+  @override
   int get length => _array.length;
 
+  @override
   bool _isWhitespace(int codeUnit) {
     return _StringBase._isOneByteWhitespace(codeUnit);
   }
@@ -951,7 +955,8 @@
     return super == other;
   }
 
-  String _substringUncheckedNative(int startIndex, int endIndex) {
+  @override
+  String _substringUncheckedInternal(int startIndex, int endIndex) {
     int length = endIndex - startIndex;
     var result = _OneByteString._withLength(length);
     for (int i = 0; i < length; i++) {
@@ -998,6 +1003,7 @@
     return result;
   }
 
+  @override
   int _copyIntoTwoByteString(_TwoByteString result, int offset) {
     final from = _array;
     final int length = from.length;
@@ -1256,6 +1262,7 @@
         super._();
 
   // Same hash as VM
+  @override
   int _computeHashCode() {
     WasmIntArray<WasmI16> array = _array;
     int length = array.length;
@@ -1290,18 +1297,32 @@
     writeIntoTwoByteString(this, index, codePoint);
   }
 
+  @override
   bool _isWhitespace(int codeUnit) {
     return _StringBase._isTwoByteWhitespace(codeUnit);
   }
 
+  @override
   int codeUnitAt(int index) => _array.readUnsigned(index);
 
+  @override
   int get length => _array.length;
 
   bool operator ==(Object other) {
     return super == other;
   }
 
+  @override
+  String _substringUncheckedInternal(int startIndex, int endIndex) {
+    int length = endIndex - startIndex;
+    var result = _TwoByteString._withLength(length);
+    for (int i = 0; i < length; i++) {
+      result._setAt(i, codeUnitAt(startIndex + i));
+    }
+    return result;
+  }
+
+  @override
   int _copyIntoTwoByteString(_TwoByteString result, int offset) {
     final from = _array;
     final int length = from.length;
diff --git a/tools/VERSION b/tools/VERSION
index 1604640..cbf25bd 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 78
+PRERELEASE 79
 PRERELEASE_PATCH 0
\ No newline at end of file