Version 2.18.0-199.0.dev

Merge commit '2885ecc42664e4972343addeb73beb892f4737ea' into 'dev'
diff --git a/DEPS b/DEPS
index c048fa6..89a6b62 100644
--- a/DEPS
+++ b/DEPS
@@ -75,23 +75,23 @@
   "gperftools_revision": "180bfa10d7cb38e8b3784d60943d50e8fcef0dcb",
 
   # Revisions of /third_party/* dependencies.
-  "args_rev": "862d929b980b993334974d38485a39d891d83918",
+  "args_rev": "73e8d3b55cbedc9765f8e266f3422d8914f8e62a",
   "async_rev": "f3ed5f690e2ec9dbe1bfc5184705575b4f6480e5",
   "bazel_worker_rev": "9710de6c9c70b1b583183db9d9721ba64e5a16fe",
-  "benchmark_harness_rev": "0530da692a5d689f4b5450a7c8d1a8abe3e2d555",
+  "benchmark_harness_rev": "0ae822e264e410e9f1e927daea68601cc54906ef",
   "boolean_selector_rev": "1d3565e2651d16566bb556955b96ea75018cbd0c",
   "boringssl_gen_rev": "ced85ef0a00bbca77ce5a91261a5f2ae61b1e62f",
   "boringssl_rev": "87f316d7748268eb56f2dc147bd593254ae93198",
   "browser-compat-data_tag": "ac8cae697014da1ff7124fba33b0b4245cc6cd1b", # v1.0.22
-  "browser_launcher_rev": "f841375ad337381e23d333b6eaaebde3d8266c68",
-  "characters_rev": "4b1d4b7737ad47cd2b8105c47e2159174010f29f",
+  "browser_launcher_rev": "981ca8847dd2b0fe022f9e742045cfb8f214d35f",
+  "characters_rev": "559755d67af2c78b9beaaeb7ca57d7c4ae0b836d",
   "chrome_rev": "19997",
   "cli_util_rev": "b0adbba89442b2ea6fef39c7a82fe79cb31e1168",
-  "clock_rev": "f594d86da123015186d5680b0d0e8255c52fc162",
-  "collection_rev": "f9b433dfc7dba5c8a987b1a8e6a9050292f5582e",
-  "convert_rev": "00b251529c074df394b3391c7e3eea3dd9e5778e",
-  "crypto_rev": "4297d240b0e1e780ec0a9eab23eaf1ad491f3e68",
-  "csslib_rev": "518761b166974537f334dbf264e7f56cb157a96a",
+  "clock_rev": "a75eb69c8e939e2e7eab70e4728da3bcf004e717",
+  "collection_rev": "414ffa1bc8ba18bd608bbf916d95715311d89ac1",
+  "convert_rev": "7145da14f9cd730e80fb4c6a10108fcfd205e8e7",
+  "crypto_rev": "223e0a62c0f762fd2b510f753861445b52e14fc3",
+  "csslib_rev": "ba2eb2d80530eedefadaade338a09c2dd60410f3",
 
   # Note: Updates to dart_style have to be coordinated with the infrastructure
   # team so that the internal formatter `tools/sdks/dart-sdk/bin/dart format`
@@ -109,60 +109,60 @@
   "devtools_rev": "51ac983d2db7eb19b3ce5956cb70b769d74fe784",
   "ffi_rev": "0c8364a728cfe4e4ba859c53b99d56b3dbe3add4",
   "file_rev": "0132eeedea2933513bf230513a766a8baeab0c4f",
-  "fixnum_rev": "3bfc2ed1eea7e7acb79ad4f17392f92c816fc5ce",
+  "fixnum_rev": "164712f6547cdfb2709b752188186baf31fd1730",
   "glob_rev": "e10eb2407c58427144004458ef85c9bbf7286e56",
-  "html_rev": "f108bce59d136c584969fd24a5006914796cf213",
+  "html_rev": "8243e967caad9932c13971af3b2a7c8f028383d5",
   "http_multi_server_rev": "35a3b947256768426090e3b1f5132e4fc23c175d",
-  "http_parser_rev": "9126ee04e77fd8e4e2e6435b503ee4dd708d7ddc",
+  "http_parser_rev": "eaa63304c333316acd114e3be7ed701d7d7ba32c",
   "http_rev": "0c2293062d7c1fa472f299da764a7dbb3895ee22",
   "icu_rev": "81d656878ec611cb0b42d52c82e9dae93920d9ba",
-  "intl_rev": "9145f308f1458f37630a1ffce3b7d3b471ebbc56",
+  "intl_rev": "e9b573679de5e703d89a242b9dca331c772979ef",
   "jinja2_rev": "2222b31554f03e62600cd7e383376a7c187967a1",
   "json_rpc_2_rev": "2de9a1f9821807fa2c85fd48e2f70b9cbcddcb67",
   "linter_rev": "1ddc70948d94f2449fec69a95e3ceb7b6b6c8348", # 1.25.0
   "lints_rev": "8294e5648ab49474541527e2911e72e4c5aefe55",
   "logging_rev": "f6979e3bc3b6e1847a08335b7eb6304e18986195",
-  "markdown_rev": "e3f4bd28c9e61b522f75f291d4d6cfcfeccd83ee", # 5.0.0
+  "markdown_rev": "73ad99eeddc1ecebd27faa3221e68c81dc8217eb", # 5.0.0
   "markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
   "matcher_rev": "12cdc5fbafd666ed908359ae215d5d0306087969",
-  "mime_rev": "c2c5ffd594674f32dc277521369da1557a1622d3",
-  "mockito_rev": "fcd6b285f7c4a631778890cf02b52a9a68171a71",
+  "mime_rev": "0a75a41445eb642674a0a271eecde78cb025ee60",
+  "mockito_rev": "25d25dab6b57ac710c0be0e759def7505b352ea7",
   "oauth2_rev": "199ebf15cbd5b07958438184f32e41c4447a57bf",
   "package_config_rev": "cff98c90acc457a3b0750f0a7da0e351a35e5d0c",
-  "path_rev": "3d41ea582f5b0b18de3d90008809b877ff3f69bc",
+  "path_rev": "7a0ed40280345b1c11df4c700c71e590738f4257",
   "ply_rev": "604b32590ffad5cbb82e4afef1d305512d06ae93",
   "pool_rev": "c40cc32eabecb9d60f1045d1403108d968805f9a",
-  "protobuf_rev": "3105588b8e515a3391ac5f0673cc06e16d0e3c0c",
+  "protobuf_rev": "3149f6f2d323e11dbcc983b7ac8b3b9e9d686293",
   "pub_rev": "c4e9ddc888c3aa89ef4462f0c4298929191e32b9",
   "pub_semver_rev": "5c0b4bfd5ca57fe16f1319c581dc8c882e9b8cb2",
   "root_certificates_rev": "692f6d6488af68e0121317a9c2c9eb393eb0ee50",
   "rust_revision": "b7856f695d65a8ebc846754f97d15814bcb1c244",
   "shelf_rev": "05f42601d22c9bfe490ceda50e812f83b7d1de77",
-  "source_map_stack_trace_rev": "8eabd96b1811e30a11d3c54c9b4afae4fb72e98f",
-  "source_maps_rev": "c07a01b8d5547ce3a47ee7a7a2b938a2bc09afe3",
-  "source_span_rev": "8ae724c3e67f5afaacead44e93ff145bfb8775c7",
-  "sse_rev": "9a54f1cdd91c8d79a6bf5ef8e849a12756607453",
+  "source_map_stack_trace_rev": "72dbf21a33293b2b8434d0a9751e36f9463981ac",
+  "source_maps_rev": "e93565b43a7b6b367789de8ffba969c4ebeeb317",
+  "source_span_rev": "24151fd80e4557a626f81f2bc0d6a2ebde172cae",
+  "sse_rev": "2df072848a6090d3ed67f30c69e86ec4d6b96cd6",
   "stack_trace_rev": "17f09c2c6845bb31c7c385acecce5befb8527a13",
-  "stream_channel_rev": "3fa3e40c75c210d617b8b943b9b8f580e9866a89",
+  "stream_channel_rev": "8e0d7ef1f4a3fb97fbd82e11cd539093f58511f3",
   "string_scanner_rev": "c637deb8d998b72a5807afbd06aba8370db725c0",
-  "sync_http_rev": "b6bd47965694dddffb6e62fb8a6c12d17c4ae4cd",
+  "sync_http_rev": "39509d69fd5a9c3da46eab48fcafdf62e6ad4580",
   "term_glyph_rev": "d0f205c67ea70eea47b9f41c8440129a72a9c86e",
   "test_descriptor_rev": "5ed5d7f6bf1191592995dcb8eedbbc17df69d386",
   "test_process_rev": "3e695bcfeab551473ddc288970f345f30e5e1375",
   "test_reflective_loader_rev": "8d0de01bbe852fea1f8e33aba907abcba50a8a1e",
-  "test_rev": "d54846bc2b5cfa4e1445fda85c5e48a00940aa68",
-  "typed_data_rev": "8b19e29bcf4077147de4d67adeabeb48270c65eb",
-  "usage_rev": "79eef484e7403f24b414354a4af2008967484e46",
-  "vector_math_rev": "1c72944e8c2f02340a1d90b32aab2e3836cef8cc",
+  "test_rev": "90ec2561a9725d54597498d51b23f1ccf09a6db1",
+  "typed_data_rev": "bb10b64f9a56b8fb49307d4465474bf1c1309f6d",
+  "usage_rev": "1d3c31e780af665fb796a27898a441fcb7d263db",
+  "vector_math_rev": "cdcee487bde4353a6ba7a29bfc7db3840426e50f",
   "watcher_rev": "e00c0ea769e32821d91c0880da8eb736839a6e6d",
   "web_components_rev": "8f57dac273412a7172c8ade6f361b407e2e4ed02",
   "web_socket_channel_rev": "99dbdc5769e19b9eeaf69449a59079153c6a8b1f",
   "WebCore_rev": "bcb10901266c884e7b3740abc597ab95373ab55c",
-  "webdev_rev": "8c814f9d89915418d8abe354ff9befec8f2906b2",
+  "webdev_rev": "a74737fc46286b3e34475e20e6e44fc92efc8916",
   "webdriver_rev": "e1a9ad671ee82e05eee463f922a34585ed2d2f15",
-  "webkit_inspection_protocol_rev": "e4965778e2837adc62354eec3a19123402997897",
-  "yaml_edit_rev": "0b74d85fac10b4fbf7d1a347debcf16c8f7b0e9c",
-  "yaml_rev": "0971c06490b9670add644ed62182acd6a5536946",
+  "webkit_inspection_protocol_rev": "57522d6b29d94903b765c757079d906555d5a171",
+  "yaml_edit_rev": "01589b3ce447b03aed991db49f1ec6445ad5476d",
+  "yaml_rev": "fda5b15692ccfa0feb7793a27fe3829b3d0f77fa",
   "zlib_rev": "64bbf988543996eb8df9a86877b32917187eba8f",
 
   # Windows deps
diff --git a/pkg/analysis_server/lib/lsp_protocol/protocol_special.dart b/pkg/analysis_server/lib/lsp_protocol/protocol_special.dart
index 610813f..7f1d192 100644
--- a/pkg/analysis_server/lib/lsp_protocol/protocol_special.dart
+++ b/pkg/analysis_server/lib/lsp_protocol/protocol_special.dart
@@ -62,7 +62,7 @@
 
 bool _alwaysTrue(_, [__]) => true;
 
-class Either2<T1, T2> {
+class Either2<T1, T2> implements ToJsonable {
   final int _which;
   final T1? _t1;
   final T2? _t2;
@@ -88,6 +88,7 @@
     return _which == 1 ? f1(_t1 as T1) : f2(_t2 as T2);
   }
 
+  @override
   Object? toJson() => map(specToJson, specToJson);
 
   @override
@@ -97,7 +98,7 @@
   bool valueEquals(o) => map((t) => t == o, (t) => t == o);
 }
 
-class Either3<T1, T2, T3> {
+class Either3<T1, T2, T3> implements ToJsonable {
   final int _which;
   final T1? _t1;
   final T2? _t2;
@@ -141,6 +142,7 @@
     }
   }
 
+  @override
   Object? toJson() => map(specToJson, specToJson, specToJson);
 
   @override
@@ -154,7 +156,7 @@
   bool valueEquals(o) => map((t) => t == o, (t) => t == o, (t) => t == o);
 }
 
-class Either4<T1, T2, T3, T4> {
+class Either4<T1, T2, T3, T4> implements ToJsonable {
   final int _which;
   final T1? _t1;
   final T2? _t2;
@@ -212,6 +214,7 @@
     }
   }
 
+  @override
   Object? toJson() => map(specToJson, specToJson, specToJson, specToJson);
 
   @override
@@ -286,7 +289,7 @@
 }
 
 abstract class ToJsonable {
-  Object toJson();
+  Object? toJson();
 }
 
 extension IncomingMessageExtension on IncomingMessage {
diff --git a/pkg/analysis_server/lib/src/cider/fixes.dart b/pkg/analysis_server/lib/src/cider/fixes.dart
index fd9efd7..fe44877 100644
--- a/pkg/analysis_server/lib/src/cider/fixes.dart
+++ b/pkg/analysis_server/lib/src/cider/fixes.dart
@@ -12,6 +12,7 @@
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/instrumentation/service.dart';
 import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:analyzer/src/dart/micro/resolve_file.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
@@ -88,7 +89,8 @@
     var result = <LibraryElement, Element>{};
     var files = _fileResolver.getFilesWithTopLevelDeclarations(name);
     for (var file in files) {
-      if (file.partOfLibrary == null) {
+      final kind = file.kind;
+      if (kind is LibraryFileStateKind) {
         var libraryElement = await _fileResolver.getLibraryByUri2(
           uriStr: file.uriStr,
         );
diff --git a/pkg/analysis_server/lib/src/lsp/constants.dart b/pkg/analysis_server/lib/src/lsp/constants.dart
index 49ae1ca..2bcebf6 100644
--- a/pkg/analysis_server/lib/src/lsp/constants.dart
+++ b/pkg/analysis_server/lib/src/lsp/constants.dart
@@ -44,8 +44,8 @@
 /// Characters to trigger formatting when format-on-type is enabled.
 const dartTypeFormattingCharacters = ['}', ';'];
 
-/// A [ProgressToken] used for reporting progress when the server is analyzing.
-final analyzingProgressToken = Either2<int, String>.t2('ANALYZING');
+/// A [ProgressToken] used for reporting progress while the server is analyzing.
+final analyzingProgressToken = ProgressToken.t2('ANALYZING');
 
 final emptyWorkspaceEdit = WorkspaceEdit();
 
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index 000c634..b34d459 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -23,10 +23,11 @@
 import 'package:analyzer/src/util/performance/operation_performance.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
-import 'package:collection/collection.dart' show groupBy;
+import 'package:collection/collection.dart'
+    show IterableNullableExtension, groupBy;
 
-class CodeActionHandler extends MessageHandler<CodeActionParams,
-    List<Either2<Command, CodeAction>>> {
+class CodeActionHandler
+    extends MessageHandler<CodeActionParams, TextDocumentCodeActionResult> {
   // Because server+plugin results are different types and we lose
   // priorities when converting them to CodeActions, store the priorities
   // against each action in an expando. This avoids wrapping CodeActions in
@@ -60,10 +61,8 @@
       CodeActionParams.jsonHandler;
 
   @override
-  Future<ErrorOr<List<Either2<Command, CodeAction>>>> handle(
-      CodeActionParams params,
-      MessageInfo message,
-      CancellationToken token) async {
+  Future<ErrorOr<TextDocumentCodeActionResult>> handle(CodeActionParams params,
+      MessageInfo message, CancellationToken token) async {
     if (!isDartDocument(params.textDocument)) {
       return success(const []);
     }
@@ -189,16 +188,16 @@
 
   /// Wraps a command in a CodeAction if the client supports it so that a
   /// CodeActionKind can be supplied.
-  Either2<Command, CodeAction> _commandOrCodeAction(
+  Either2<CodeAction, Command> _commandOrCodeAction(
     bool supportsLiteralCodeActions,
     CodeActionKind kind,
     Command command,
   ) {
     return supportsLiteralCodeActions
-        ? Either2<Command, CodeAction>.t2(
+        ? Either2<CodeAction, Command>.t1(
             CodeAction(title: command.title, kind: kind, command: command),
           )
-        : Either2<Command, CodeAction>.t1(command);
+        : Either2<CodeAction, Command>.t2(command);
   }
 
   /// Creates a CodeAction to apply this assist. Note: This code will fetch the
@@ -280,7 +279,7 @@
     }).toList();
   }
 
-  Future<List<Either2<Command, CodeAction>>> _getAssistActions(
+  Future<TextDocumentCodeActionResult> _getAssistActions(
     bool Function(CodeActionKind?) shouldIncludeKind,
     bool supportsLiteralCodeActions,
     String path,
@@ -323,7 +322,7 @@
 
       return dedupedCodeActions
           .where((action) => shouldIncludeKind(action.kind))
-          .map((action) => Either2<Command, CodeAction>.t2(action))
+          .map((action) => Either2<CodeAction, Command>.t1(action))
           .toList();
     } on InconsistentAnalysisException {
       // If an InconsistentAnalysisException occurs, it's likely the user modified
@@ -333,7 +332,7 @@
     }
   }
 
-  Future<ErrorOr<List<Either2<Command, CodeAction>>>> _getCodeActions(
+  Future<ErrorOr<TextDocumentCodeActionResult>> _getCodeActions(
     OperationPerformanceImpl performance,
     bool Function(CodeActionKind?) shouldIncludeKind,
     bool Function(CodeActionKind?) shouldIncludeAnyOfKind,
@@ -376,12 +375,12 @@
               offset, supportedDiagnosticTags, range, unit),
         ),
     ]);
-    final flatResults = results.expand((x) => x).toList();
+    final flatResults = results.whereNotNull().expand((x) => x).toList();
 
     return success(flatResults);
   }
 
-  Future<List<Either2<Command, CodeAction>>> _getFixActions(
+  Future<TextDocumentCodeActionResult> _getFixActions(
     bool Function(CodeActionKind?) shouldIncludeKind,
     bool supportsLiteralCodeActions,
     String path,
@@ -455,7 +454,7 @@
 
       return dedupedActions
           .where((action) => shouldIncludeKind(action.kind))
-          .map((action) => Either2<Command, CodeAction>.t2(action))
+          .map((action) => Either2<CodeAction, Command>.t1(action))
           .toList();
     } on InconsistentAnalysisException {
       // If an InconsistentAnalysisException occurs, it's likely the user modified
@@ -520,7 +519,7 @@
     return pluginFixes;
   }
 
-  Future<List<Either2<Command, CodeAction>>> _getRefactorActions(
+  Future<TextDocumentCodeActionResult> _getRefactorActions(
     bool Function(CodeActionKind) shouldIncludeKind,
     bool supportsLiteralCodeActions,
     String path,
@@ -537,7 +536,7 @@
 
     /// Helper to create refactors that execute commands provided with
     /// the current file, location and document version.
-    Either2<Command, CodeAction> createRefactor(
+    Either2<CodeAction, Command> createRefactor(
       CodeActionKind actionKind,
       String name,
       RefactoringKind refactorKind, [
@@ -564,7 +563,7 @@
     }
 
     try {
-      final refactorActions = <Either2<Command, CodeAction>>[];
+      final refactorActions = <Either2<CodeAction, Command>>[];
 
       // Extracts
       if (shouldIncludeKind(CodeActionKind.RefactorExtract)) {
@@ -641,7 +640,7 @@
 
   /// Gets "Source" CodeActions, which are actions that apply to whole files of
   /// source such as Sort Members and Organise Imports.
-  Future<List<Either2<Command, CodeAction>>> _getSourceActions(
+  Future<TextDocumentCodeActionResult> _getSourceActions(
     bool Function(CodeActionKind) shouldIncludeKind,
     bool supportsLiteralCodeActions,
     bool supportsApplyEdit,
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
index a066a4c..530a30f 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
@@ -22,8 +22,7 @@
 import 'package:collection/collection.dart';
 
 class DefinitionHandler extends MessageHandler<TextDocumentPositionParams,
-        Either2<List<Location>, List<LocationLink>>>
-    with LspPluginRequestHandlerMixin {
+    TextDocumentDefinitionResult> with LspPluginRequestHandlerMixin {
   DefinitionHandler(super.server);
   @override
   Method get handlesMessage => Method.textDocument_definition;
@@ -69,7 +68,7 @@
   }
 
   @override
-  Future<ErrorOr<Either2<List<Location>, List<LocationLink>>>> handle(
+  Future<ErrorOr<TextDocumentDefinitionResult>> handle(
       TextDocumentPositionParams params,
       MessageInfo message,
       CancellationToken token) async {
@@ -89,9 +88,7 @@
       // If there is no lineInfo, the request cannot be translated from LSP line/col
       // to server offset/length.
       if (lineInfo == null) {
-        return success(
-          Either2<List<Location>, List<LocationLink>>.t1(const []),
-        );
+        return success(TextDocumentDefinitionResult.t2(const []));
       }
 
       final offset = toOffset(lineInfo, pos);
@@ -107,9 +104,7 @@
         final mergedTargets = mergedResults?.targets ?? [];
 
         if (mergedResults == null) {
-          return success(
-            Either2<List<Location>, List<LocationLink>>.t1(const []),
-          );
+          return success(TextDocumentDefinitionResult.t2(const []));
         }
 
         // Convert and filter the results using the correct type of Location class
@@ -129,9 +124,7 @@
             (LocationLink element) => element.targetSelectionRange,
           );
 
-          return success(
-            Either2<List<Location>, List<LocationLink>>.t2(results),
-          );
+          return success(TextDocumentDefinitionResult.t2(results));
         } else {
           final convertedResults = convert(
             mergedTargets,
@@ -147,7 +140,7 @@
           );
 
           return success(
-            Either2<List<Location>, List<LocationLink>>.t1(results),
+            TextDocumentDefinitionResult.t1(Definition.t1(results)),
           );
         }
       });
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart
index 1e67440..4f0a96c 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart
@@ -12,7 +12,7 @@
 import 'package:analyzer/source/line_info.dart';
 
 class DocumentSymbolHandler extends MessageHandler<DocumentSymbolParams,
-    Either2<List<DocumentSymbol>, List<SymbolInformation>>?> {
+    TextDocumentDocumentSymbolResult> {
   DocumentSymbolHandler(super.server);
   @override
   Method get handlesMessage => Method.textDocument_documentSymbol;
@@ -22,13 +22,14 @@
       DocumentSymbolParams.jsonHandler;
 
   @override
-  Future<ErrorOr<Either2<List<DocumentSymbol>, List<SymbolInformation>>?>>
-      handle(DocumentSymbolParams params, MessageInfo message,
-          CancellationToken token) async {
+  Future<ErrorOr<TextDocumentDocumentSymbolResult>> handle(
+      DocumentSymbolParams params,
+      MessageInfo message,
+      CancellationToken token) async {
     final clientCapabilities = server.clientCapabilities;
     if (clientCapabilities == null || !isDartDocument(params.textDocument)) {
       return success(
-        Either2<List<DocumentSymbol>, List<SymbolInformation>>.t2([]),
+        TextDocumentDocumentSymbolResult.t2([]),
       );
     }
 
@@ -85,7 +86,7 @@
     );
   }
 
-  ErrorOr<Either2<List<DocumentSymbol>, List<SymbolInformation>>?> _getSymbols(
+  ErrorOr<TextDocumentDocumentSymbolResult> _getSymbols(
     LspClientCapabilities capabilities,
     String path,
     ResolvedUnitResult unit,
@@ -101,7 +102,7 @@
         return success(null);
       }
       return success(
-        Either2<List<DocumentSymbol>, List<SymbolInformation>>.t1(
+        TextDocumentDocumentSymbolResult.t1(
           children
               .map((child) => _asDocumentSymbol(
                   capabilities.documentSymbolKinds, unit.lineInfo, child))
@@ -133,9 +134,7 @@
 
       outline.children?.forEach(addSymbol);
 
-      return success(
-        Either2<List<DocumentSymbol>, List<SymbolInformation>>.t2(allSymbols),
-      );
+      return success(TextDocumentDocumentSymbolResult.t2(allSymbols));
     }
   }
 }
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart
index 98d4e5f..facbfd8 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart
@@ -11,13 +11,8 @@
 import 'package:analysis_server/src/services/refactoring/rename_unit_member.dart';
 import 'package:analyzer/dart/element/element.dart';
 
-// TODO(dantup): Generate typedefs in protocol_generated for all named types
-//   that map onto unions like this after the switch to JSON spec.
-typedef PrepareRenameResult
-    = Either3<Range, PlaceholderAndRange, PrepareRenameResult2>;
-
-class PrepareRenameHandler
-    extends MessageHandler<TextDocumentPositionParams, PrepareRenameResult?> {
+class PrepareRenameHandler extends MessageHandler<TextDocumentPositionParams,
+    TextDocumentPrepareRenameResult> {
   PrepareRenameHandler(super.server);
   @override
   Method get handlesMessage => Method.textDocument_prepareRename;
@@ -27,7 +22,7 @@
       TextDocumentPositionParams.jsonHandler;
 
   @override
-  Future<ErrorOr<PrepareRenameResult?>> handle(
+  Future<ErrorOr<TextDocumentPrepareRenameResult>> handle(
       TextDocumentPositionParams params,
       MessageInfo message,
       CancellationToken token) async {
@@ -66,7 +61,7 @@
             ServerErrorCodes.RenameNotValid, initStatus.problem!.message, null);
       }
 
-      return success(PrepareRenameResult.t2(PlaceholderAndRange(
+      return success(TextDocumentPrepareRenameResult.t1(PlaceholderAndRange(
         range: toRange(
           unit.result.lineInfo,
           // If the offset is set to -1 it means there is no location for the
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart
index 59cf2cc..929a960 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart
@@ -15,12 +15,9 @@
 import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
 import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
 
-typedef _LocationsOrLinks = Either2<List<Location>, List<LocationLink>>;
-
-class TypeDefinitionHandler
-    extends MessageHandler<TypeDefinitionParams, _LocationsOrLinks>
-    with LspPluginRequestHandlerMixin {
-  static const _emptyResult = _LocationsOrLinks.t1([]);
+class TypeDefinitionHandler extends MessageHandler<TypeDefinitionParams,
+    TextDocumentTypeDefinitionResult> with LspPluginRequestHandlerMixin {
+  static const _emptyResult = TextDocumentTypeDefinitionResult.t2([]);
 
   TypeDefinitionHandler(super.server);
 
@@ -35,8 +32,10 @@
   // The private type in the return type is dictated by the signature of the
   // super-method and the class's super-class.
   // ignore: library_private_types_in_public_api
-  Future<ErrorOr<_LocationsOrLinks>> handle(TypeDefinitionParams params,
-      MessageInfo message, CancellationToken token) async {
+  Future<ErrorOr<TextDocumentTypeDefinitionResult>> handle(
+      TypeDefinitionParams params,
+      MessageInfo message,
+      CancellationToken token) async {
     if (!isDartDocument(params.textDocument)) {
       return success(_emptyResult);
     }
@@ -90,13 +89,14 @@
         }
 
         if (supportsLocationLink) {
-          return success(_LocationsOrLinks.t2([
+          return success(TextDocumentTypeDefinitionResult.t2([
             _toLocationLink(
                 result.lineInfo, targetLineInfo, node, element, location)
           ]));
         } else {
-          return success(
-              _LocationsOrLinks.t1([_toLocation(location, targetLineInfo)]));
+          return success(TextDocumentTypeDefinitionResult.t1(
+            Definition.t2(_toLocation(location, targetLineInfo)),
+          ));
         }
       });
     });
diff --git a/pkg/analysis_server/lib/src/lsp/progress.dart b/pkg/analysis_server/lib/src/lsp/progress.dart
index 370f54e..17b3e3a 100644
--- a/pkg/analysis_server/lib/src/lsp/progress.dart
+++ b/pkg/analysis_server/lib/src/lsp/progress.dart
@@ -16,7 +16,7 @@
   /// Creates a reporter for a token that was supplied by the client and does
   /// not need creating prior to use.
   factory ProgressReporter.clientProvided(
-          LspAnalysisServer server, Either2<int, String> token) =>
+          LspAnalysisServer server, ProgressToken token) =>
       _TokenProgressReporter(server, token);
 
   /// Creates a reporter for a new token that must be created prior to being
@@ -24,7 +24,7 @@
   ///
   /// If [token] is not supplied, a random identifier will be used.
   factory ProgressReporter.serverCreated(LspAnalysisServer server,
-          [Either2<int, String>? token]) =>
+          [ProgressToken? token]) =>
       _ServerCreatedProgressReporter(server, token);
 
   ProgressReporter._();
@@ -49,10 +49,10 @@
 
   _ServerCreatedProgressReporter(
     LspAnalysisServer server,
-    Either2<int, String>? token,
+    ProgressToken? token,
   ) : super(
           server,
-          token ?? Either2<int, String>.t2(_randomTokenIdentifier()),
+          token ?? ProgressToken.t2(_randomTokenIdentifier()),
         );
 
   @override
@@ -101,7 +101,7 @@
 
 class _TokenProgressReporter extends ProgressReporter {
   final LspAnalysisServer _server;
-  final Either2<int, String> _token;
+  final ProgressToken _token;
   bool _needsEnd = false;
 
   _TokenProgressReporter(this._server, this._token) : super._();
diff --git a/pkg/analysis_server/lib/src/lsp/source_edits.dart b/pkg/analysis_server/lib/src/lsp/source_edits.dart
index 99021fb..ac2a8b8 100644
--- a/pkg/analysis_server/lib/src/lsp/source_edits.dart
+++ b/pkg/analysis_server/lib/src/lsp/source_edits.dart
@@ -26,10 +26,7 @@
 /// changes into account, this will also apply the edits to [oldContent].
 ErrorOr<Pair<String, List<plugin.SourceEdit>>> applyAndConvertEditsToServer(
   String oldContent,
-  List<
-          Either2<TextDocumentContentChangeEvent1,
-              TextDocumentContentChangeEvent2>>
-      changes, {
+  List<TextDocumentContentChangeEvent> changes, {
   bool failureIsCritical = false,
 }) {
   var newContent = oldContent;
diff --git a/pkg/analysis_server/test/analysis/notification_navigation_test.dart b/pkg/analysis_server/test/analysis/notification_navigation_test.dart
index a397b81..2d1b4e5 100644
--- a/pkg/analysis_server/test/analysis/notification_navigation_test.dart
+++ b/pkg/analysis_server/test/analysis/notification_navigation_test.dart
@@ -377,12 +377,13 @@
 class BBB {}
 ''');
     await prepareNavigation();
-    // has region for complete "A.named"
-    assertHasRegionString('A.named');
+    // has no region for complete "A.named"
+    assertNoRegion('A.named', 'A.named'.length);
+    // has separate regions for "A" and "named"
+    assertHasRegion('A.named(', 'A'.length);
+    assertHasTarget('A {');
+    assertHasRegion('named(', 'named'.length);
     assertHasTarget('named(BBB');
-    // no separate regions for "A" and "named"
-    assertNoRegion('A.named(', 'A'.length);
-    assertNoRegion('named(', 'named'.length);
     // validate that we don't forget to resolve parameters
     assertHasRegionTarget('BBB p', 'BBB {}');
   }
@@ -413,7 +414,7 @@
 }
 ''');
     await prepareNavigation();
-    assertHasRegionTarget('B<A>.named;', 'named();');
+    assertHasRegionTarget('B<A>.named;', 'B<T> {');
     assertHasRegionTarget('named;', 'named();');
     assertHasRegionTarget('A>', 'A {}');
   }
@@ -428,7 +429,7 @@
 }
 ''');
     await prepareNavigation();
-    assertHasRegionTarget('A.new;', 'A();');
+    assertHasRegionTarget('A.new;', 'A {');
     assertHasRegionTarget('new;', 'A();');
   }
 
@@ -442,7 +443,7 @@
 }
 ''');
     await prepareNavigation();
-    assertHasRegionTarget('A.new;', 'new();');
+    assertHasRegionTarget('A.new;', 'A {');
     assertHasRegionTarget('new;', 'new();');
   }
 
@@ -503,7 +504,7 @@
     await prepareNavigation();
     {
       assertHasRegionString('B.named;', 'B'.length);
-      assertHasTarget('named();');
+      assertHasTarget('B {');
     }
     {
       assertHasRegionString('named;', 'named'.length);
@@ -525,7 +526,7 @@
     await prepareNavigation();
     {
       assertHasRegion('C<A>');
-      assertHasTarget('named() {}');
+      assertHasTarget('C<T> {');
     }
     {
       assertHasRegion('A>.named');
@@ -1015,7 +1016,7 @@
     await prepareNavigation();
     {
       assertHasRegionString('A.named();', 'A'.length);
-      assertHasTarget('named() {}');
+      assertHasTarget('A {');
     }
     {
       assertHasRegionString('named();', 'named'.length);
@@ -1036,7 +1037,7 @@
     await prepareNavigation();
     {
       assertHasRegionString('B<A>', 'B'.length);
-      assertHasTarget('named() {}');
+      assertHasTarget('B<T> {');
     }
     {
       assertHasRegion('A>.named');
diff --git a/pkg/analysis_server/test/integration/analysis/navigation_test.dart b/pkg/analysis_server/test/integration/analysis/navigation_test.dart
index 8413241..91a6e69 100644
--- a/pkg/analysis_server/test/integration/analysis/navigation_test.dart
+++ b/pkg/analysis_server/test/integration/analysis/navigation_test.dart
@@ -105,9 +105,7 @@
     checkLocal('Class<int>', 'Class<TypeParameter>', ElementKind.CLASS);
     checkRemote("'test2.dart';", r'test2.dart$', ElementKind.COMPILATION_UNIT);
     checkLocal(
-        'Class<int>.constructor',
-        'constructor(); /* constructor declaration */',
-        ElementKind.CONSTRUCTOR);
+        'Class<int>.constructor', 'Class<TypeParameter>', ElementKind.CLASS);
     checkLocal(
         'constructor(); // usage',
         'constructor(); /* constructor declaration */',
diff --git a/pkg/analysis_server/test/lsp/code_actions_abstract.dart b/pkg/analysis_server/test/lsp/code_actions_abstract.dart
index 3d228b7..2f059a1 100644
--- a/pkg/analysis_server/test/lsp/code_actions_abstract.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_abstract.dart
@@ -89,7 +89,7 @@
   Future verifyCodeActionEdits(Either2<Command, CodeAction> codeAction,
       String content, String expectedContent,
       {bool expectDocumentChanges = false,
-      Either2<int, String>? workDoneToken}) async {
+      ProgressToken? workDoneToken}) async {
     final command = codeAction.map(
       (command) => command,
       (codeAction) => codeAction.command!,
@@ -106,7 +106,7 @@
   Future<void> verifyCommandEdits(
       Command command, String content, String expectedContent,
       {bool expectDocumentChanges = false,
-      Either2<int, String>? workDoneToken}) async {
+      ProgressToken? workDoneToken}) async {
     ApplyWorkspaceEditParams? editParams;
 
     final commandResponse = await handleExpectedRequest<Object?,
diff --git a/pkg/analysis_server/test/lsp/definition_test.dart b/pkg/analysis_server/test/lsp/definition_test.dart
index aa59e35..1577e53 100644
--- a/pkg/analysis_server/test/lsp/definition_test.dart
+++ b/pkg/analysis_server/test/lsp/definition_test.dart
@@ -55,6 +55,62 @@
     expect(loc.uri, equals(referencedFileUri.toString()));
   }
 
+  Future<void> test_atDeclaration_class() async {
+    final contents = '''
+class [[^A]] {}
+    ''';
+
+    await testContents(contents);
+  }
+
+  Future<void> test_atDeclaration_constructorNamed() async {
+    final contents = '''
+class A {
+  A.[[^named]]() {}
+}
+    ''';
+
+    await testContents(contents);
+  }
+
+  Future<void> test_atDeclaration_constructorNamed_typeName() async {
+    final contents = '''
+class [[A]] {
+  ^A.named() {}
+}
+    ''';
+
+    await testContents(contents);
+  }
+
+  Future<void> test_atDeclaration_defaultConstructor() async {
+    final contents = '''
+class A {
+  [[^A]]() {}
+}
+    ''';
+
+    await testContents(contents);
+  }
+
+  Future<void> test_atDeclaration_function() async {
+    final contents = '''
+void [[^f]]() {}
+    ''';
+
+    await testContents(contents);
+  }
+
+  Future<void> test_atDeclaration_method() async {
+    final contents = '''
+class A {
+  void [[^f]]() {}
+}
+    ''';
+
+    await testContents(contents);
+  }
+
   Future<void> test_comment_adjacentReference() async {
     /// Computing Dart navigation locates a node at the provided offset then
     /// returns all navigation regions inside it. This test ensures we filter
@@ -89,7 +145,7 @@
     await testContents(contents);
   }
 
-  Future<void> test_constructor_named() async {
+  Future<void> test_constructorNamed() async {
     final contents = '''
 f() {
   final a = A.named^();
@@ -103,6 +159,20 @@
     await testContents(contents);
   }
 
+  Future<void> test_constructorNamed_typeName() async {
+    final contents = '''
+f() {
+  final a = A^.named();
+}
+
+class [[A]] {
+  A.named();
+}
+''';
+
+    await testContents(contents);
+  }
+
   Future<void> test_fromPlugins() async {
     final pluginAnalyzedFilePath = join(projectFolderPath, 'lib', 'foo.foo');
     final pluginAnalyzedFileUri = Uri.file(pluginAnalyzedFilePath);
diff --git a/pkg/analysis_server/test/lsp/document_changes_test.dart b/pkg/analysis_server/test/lsp/document_changes_test.dart
index db37854..db65c63 100644
--- a/pkg/analysis_server/test/lsp/document_changes_test.dart
+++ b/pkg/analysis_server/test/lsp/document_changes_test.dart
@@ -35,15 +35,13 @@
   Future<void> test_documentChange_notifiesPlugins() async {
     await _initializeAndOpen();
     await changeFile(2, mainFileUri, [
-      Either2<TextDocumentContentChangeEvent1,
-          TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
+      TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
         range: Range(
             start: Position(line: 0, character: 6),
             end: Position(line: 0, character: 9)),
         text: 'Bar',
       )),
-      Either2<TextDocumentContentChangeEvent1,
-          TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
+      TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
         range: Range(
             start: Position(line: 1, character: 21),
             end: Position(line: 1, character: 24)),
@@ -63,15 +61,13 @@
   Future<void> test_documentChange_updatesOverlay() async {
     await _initializeAndOpen();
     await changeFile(2, mainFileUri, [
-      Either2<TextDocumentContentChangeEvent1,
-          TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
+      TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
         range: Range(
             start: Position(line: 0, character: 6),
             end: Position(line: 0, character: 9)),
         text: 'Bar',
       )),
-      Either2<TextDocumentContentChangeEvent1,
-          TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
+      TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
         range: Range(
             start: Position(line: 1, character: 21),
             end: Position(line: 1, character: 24)),
diff --git a/pkg/analysis_server/test/lsp/file_modification_test.dart b/pkg/analysis_server/test/lsp/file_modification_test.dart
index b4fc00a..80cbf07 100644
--- a/pkg/analysis_server/test/lsp/file_modification_test.dart
+++ b/pkg/analysis_server/test/lsp/file_modification_test.dart
@@ -29,8 +29,7 @@
     // to alert the user to something failing.
     final error = await expectErrorNotification(() async {
       await changeFile(222, mainFileUri, [
-        Either2<TextDocumentContentChangeEvent1,
-            TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
+        TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
           range: Range(
               start: Position(line: 999, character: 999),
               end: Position(line: 999, character: 999)),
@@ -63,8 +62,7 @@
     await openFile(mainFileUri, initialContent);
     await changeFile(222, mainFileUri, [
       // Replace line1:5-1:8 with spaces.
-      Either2<TextDocumentContentChangeEvent1,
-          TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
+      TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
         range: Range(
             start: Position(line: 1, character: 5),
             end: Position(line: 1, character: 8)),
@@ -81,8 +79,8 @@
     // It's not valid for a client to send a request to modify a file that it
     // has not opened, but Visual Studio has done it in the past so we should
     // ensure it generates an obvious error that the user can understand.
-    final simpleEdit = Either2<TextDocumentContentChangeEvent1,
-        TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
+    final simpleEdit =
+        TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
       range: Range(
           start: Position(line: 1, character: 1),
           end: Position(line: 1, character: 1)),
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 2799810..9a119b2 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -626,8 +626,7 @@
 
   /// A progress token used in tests where the client-provides the token, which
   /// should not be validated as being created by the server first.
-  final clientProvidedTestWorkDoneToken =
-      Either2<int, String>.t2('client-test');
+  final clientProvidedTestWorkDoneToken = ProgressToken.t2('client-test');
 
   int _id = 0;
   late String projectFolderPath,
@@ -646,7 +645,7 @@
   /// null if an initialization request has not yet been sent.
   ClientCapabilities? _clientCapabilities;
 
-  final validProgressTokens = <Either2<num, String>>{};
+  final validProgressTokens = <ProgressToken>{};
 
   /// Whether to include 'clientRequestTime' fields in outgoing messages.
   bool includeClientRequestTime = false;
@@ -822,10 +821,7 @@
   Future changeFile(
     int newVersion,
     Uri uri,
-    List<
-            Either2<TextDocumentContentChangeEvent1,
-                TextDocumentContentChangeEvent2>>
-        changes,
+    List<TextDocumentContentChangeEvent> changes,
   ) async {
     var notification = makeNotification(
       Method.textDocument_didChange,
@@ -872,7 +868,7 @@
   Future<T> executeCommand<T>(
     Command command, {
     T Function(Map<String, Object?>)? decoder,
-    Either2<int, String>? workDoneToken,
+    ProgressToken? workDoneToken,
   }) async {
     final request = makeRequest(
       Method.workspace_executeCommand,
@@ -1294,7 +1290,7 @@
     return expectSuccessfulResponseTo(request, Location.fromJson);
   }
 
-  Future<Either2<List<Location>, List<LocationLink>>> getTypeDefinition(
+  Future<TextDocumentTypeDefinitionResult> getTypeDefinition(
       Uri uri, Position pos) {
     final request = makeRequest(
       Method.textDocument_typeDefinition,
@@ -1303,30 +1299,51 @@
         position: pos,
       ),
     );
+
+    // TextDocumentTypeDefinitionResult is a nested Either, so we need to handle
+    // nested fromJson/canParse here.
+    // TextDocumentTypeDefinitionResult: Either2<Definition, List<DefinitionLink>>?
+    // Definition: Either2<List<Location>, Location>
+
+    // Definition = Either2<List<Location>, Location>
+    final definitionCanParse = _generateCanParseFor(
+      _canParseList(Location.canParse),
+      Location.canParse,
+    );
+    final definitionFromJson = _generateFromJsonFor(
+      _canParseList(Location.canParse),
+      _fromJsonList(Location.fromJson),
+      Location.canParse,
+      Location.fromJson,
+    );
+
     return expectSuccessfulResponseTo(
       request,
       _generateFromJsonFor(
-          _canParseList(Location.canParse),
-          _fromJsonList(Location.fromJson),
-          _canParseList(LocationLink.canParse),
-          _fromJsonList(LocationLink.fromJson)),
+          definitionCanParse,
+          definitionFromJson,
+          _canParseList(DefinitionLink.canParse),
+          _fromJsonList(DefinitionLink.fromJson)),
     );
   }
 
   Future<List<Location>> getTypeDefinitionAsLocation(
       Uri uri, Position pos) async {
-    final results = await getTypeDefinition(uri, pos);
+    final results = (await getTypeDefinition(uri, pos))!;
     return results.map(
-      (locations) => locations,
-      (locationLinks) => throw 'Expected List<Location> got List<LocationLink>',
+      (locationOrList) => locationOrList.map(
+        (locations) => locations,
+        (location) => [location],
+      ),
+      (locationLinks) => throw 'Expected Locations, got LocationLinks',
     );
   }
 
   Future<List<LocationLink>> getTypeDefinitionAsLocationLinks(
       Uri uri, Position pos) async {
-    final results = await getTypeDefinition(uri, pos);
+    final results = (await getTypeDefinition(uri, pos))!;
     return results.map(
-      (locations) => throw 'Expected List<LocationLink> got List<Location>',
+      (locationOrList) => throw 'Expected LocationLinks, got Locations',
       (locationLinks) => locationLinks,
     );
   }
@@ -1728,8 +1745,7 @@
       newVersion,
       uri,
       [
-        Either2<TextDocumentContentChangeEvent1,
-                TextDocumentContentChangeEvent2>.t2(
+        TextDocumentContentChangeEvent.t2(
             TextDocumentContentChangeEvent2(text: content))
       ],
     );
@@ -1928,15 +1944,17 @@
   String withoutRangeMarkers(String contents) =>
       contents.replaceAll(rangeMarkerStart, '').replaceAll(rangeMarkerEnd, '');
 
-  bool Function(List<dynamic>, LspJsonReporter) _canParseList<T>(
-          bool Function(Map<String, dynamic>, LspJsonReporter) canParse) =>
-      (input, reporter) => input
-          .cast<Map<String, dynamic>>()
-          .every((item) => canParse(item, reporter));
+  bool Function(Object?, LspJsonReporter) _canParseList<T>(
+          bool Function(Map<String, Object?>, LspJsonReporter) canParse) =>
+      (input, reporter) =>
+          input is List &&
+          input
+              .cast<Map<String, Object?>>()
+              .every((item) => canParse(item, reporter));
 
-  List<T> Function(List<dynamic>) _fromJsonList<T>(
-          T Function(Map<String, dynamic>) fromJson) =>
-      (input) => input.cast<Map<String, dynamic>>().map(fromJson).toList();
+  List<T> Function(List<Object?>) _fromJsonList<T>(
+          T Function(Map<String, Object?>) fromJson) =>
+      (input) => input.cast<Map<String, Object?>>().map(fromJson).toList();
 
   Future<void> _handleProgress(NotificationMessage request) async {
     final params =
@@ -1974,22 +1992,32 @@
         notification.method == Method.window_showMessage;
   }
 
+  /// Creates a `canParse()` function for an `Either2<T1, T2>` using
+  /// the `canParse` function for each type.
+  static bool Function(Object?, LspJsonReporter) _generateCanParseFor<T1, T2>(
+    bool Function(Object?, LspJsonReporter) canParse1,
+    bool Function(Object?, LspJsonReporter) canParse2,
+  ) {
+    return (input, reporter) =>
+        canParse1(input, reporter) || canParse2(input, reporter);
+  }
+
   /// Creates a `fromJson()` function for an `Either2<T1, T2>` using
   /// the `canParse` and `fromJson` functions for each type.
-  static Either2<T1, T2> Function(R) _generateFromJsonFor<T1, T2, R>(
-      bool Function(R, LspJsonReporter) canParse1,
-      T1 Function(R) fromJson1,
-      bool Function(R, LspJsonReporter) canParse2,
-      T2 Function(R) fromJson2,
+  static Either2<T1, T2> Function(Object?) _generateFromJsonFor<T1, T2, R1, R2>(
+      bool Function(Object?, LspJsonReporter) canParse1,
+      T1 Function(R1) fromJson1,
+      bool Function(Object?, LspJsonReporter) canParse2,
+      T2 Function(R2) fromJson2,
       [LspJsonReporter? reporter]) {
     reporter ??= nullLspJsonReporter;
     return (input) {
       reporter!;
       if (canParse1(input, reporter)) {
-        return Either2<T1, T2>.t1(fromJson1(input));
+        return Either2<T1, T2>.t1(fromJson1(input as R1));
       }
       if (canParse2(input, reporter)) {
-        return Either2<T1, T2>.t2(fromJson2(input));
+        return Either2<T1, T2>.t2(fromJson2(input as R2));
       }
       throw '$input was not one of ($T1, $T2)';
     };
diff --git a/pkg/analysis_server/test/lsp/server_test.dart b/pkg/analysis_server/test/lsp/server_test.dart
index b29f98e..8bcb631 100644
--- a/pkg/analysis_server/test/lsp/server_test.dart
+++ b/pkg/analysis_server/test/lsp/server_test.dart
@@ -58,13 +58,11 @@
     // client and server are out of sync and we expect the server to shut down.
     final error = await expectErrorNotification(() async {
       await changeFile(222, mainFileUri, [
-        Either2<TextDocumentContentChangeEvent1,
-                TextDocumentContentChangeEvent2>.t1(
-            TextDocumentContentChangeEvent1(
-                range: Range(
-                    start: Position(line: 99, character: 99),
-                    end: Position(line: 99, character: 99)),
-                text: ' ')),
+        TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
+            range: Range(
+                start: Position(line: 99, character: 99),
+                end: Position(line: 99, character: 99)),
+            text: ' ')),
       ]);
     });
 
diff --git a/pkg/analysis_server/test/src/cider/cider_service.dart b/pkg/analysis_server/test/src/cider/cider_service.dart
index 5fb6ccc..cdfefa6 100644
--- a/pkg/analysis_server/test/src/cider/cider_service.dart
+++ b/pkg/analysis_server/test/src/cider/cider_service.dart
@@ -44,6 +44,7 @@
       prefetchFiles: null,
       workspace: workspace,
       byteStore: MemoryByteStore(),
+      isGenerated: (_) => false,
     );
     fileResolver.testView = FileResolverTestView();
   }
diff --git a/pkg/analysis_server/test/tool/lsp_spec/json_test.dart b/pkg/analysis_server/test/tool/lsp_spec/json_test.dart
index 9275e6d..6cfdde6 100644
--- a/pkg/analysis_server/test/tool/lsp_spec/json_test.dart
+++ b/pkg/analysis_server/test/tool/lsp_spec/json_test.dart
@@ -10,6 +10,10 @@
 
 void main() {
   group('toJson', () {
+    final start = Position(line: 1, character: 1);
+    final end = Position(line: 2, character: 2);
+    final range = Range(start: start, end: end);
+
     test('returns correct JSON for a union', () {
       final num = Either2.t1(1);
       final string = Either2.t2('Test');
@@ -26,6 +30,29 @@
       expect(output, equals('{"id":1,"jsonrpc":"test","method":"shutdown"}'));
     });
 
+    test('returns correct output for nested union types', () {
+      final message = ResponseMessage(
+        id: Either2<int, String>.t1(1),
+        result:
+            Either2<Either2<List<Location>, Location>, List<LocationLink>>.t1(
+                Either2<List<Location>, Location>.t1([
+          Location(
+            range: range,
+            uri: '!uri',
+          )
+        ])),
+        jsonrpc: jsonRpcVersion,
+      );
+      final output = json.encode(message.toJson());
+      expect(
+          output,
+          equals(
+            '{"id":1,"jsonrpc":"2.0",'
+            '"result":[{"range":{"end":{"character":2,"line":2},"start":{"character":1,"line":1}},'
+            '"uri":"!uri"}]}',
+          ));
+    });
+
     test('returns correct output for union types containing interface types',
         () {
       final params = Either2<String, TextDocumentItem>.t2(TextDocumentItem(
@@ -38,9 +65,6 @@
     });
 
     test('returns correct output for types with lists', () {
-      final start = Position(line: 1, character: 1);
-      final end = Position(line: 2, character: 2);
-      final range = Range(start: start, end: end);
       final location = Location(uri: 'y-uri', range: range);
       final codeAction = Diagnostic(
         range: range,
diff --git a/pkg/analyzer/lib/src/context/context.dart b/pkg/analyzer/lib/src/context/context.dart
index e6b7d64..1b9c657 100644
--- a/pkg/analyzer/lib/src/context/context.dart
+++ b/pkg/analyzer/lib/src/context/context.dart
@@ -24,22 +24,12 @@
   }
 
   @override
-  set analysisOptions(AnalysisOptions options) {
-    throw StateError('Cannot be changed.');
-  }
-
-  @override
   DeclaredVariables get declaredVariables {
     return _synchronousSession.declaredVariables;
   }
 
   bool get hasTypeProvider => _synchronousSession.hasTypeProvider;
 
-  @override
-  set sourceFactory(SourceFactory factory) {
-    throw StateError('Cannot be changed.');
-  }
-
   TypeProviderImpl get typeProviderLegacy {
     return _synchronousSession.typeProviderLegacy;
   }
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 611e4fe..f80867d 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -86,7 +86,7 @@
 /// TODO(scheglov) Clean up the list of implicitly analyzed files.
 class AnalysisDriver implements AnalysisDriverGeneric {
   /// The version of data format, should be incremented on every format change.
-  static const int DATA_VERSION = 223;
+  static const int DATA_VERSION = 224;
 
   /// The number of exception contexts allowed to write. Once this field is
   /// zero, we stop writing any new exception contexts in this process.
@@ -116,15 +116,17 @@
   /// the content from the file.
   final FileContentCache _fileContentCache;
 
+  late final StoredFileContentStrategy _fileContentStrategy;
+
   /// The analysis options to analyze with.
-  AnalysisOptionsImpl _analysisOptions;
+  final AnalysisOptionsImpl _analysisOptions;
 
   /// The [Packages] object with packages and their language versions.
-  Packages _packages;
+  final Packages _packages;
 
   /// The [SourceFactory] is used to resolve URIs to paths and restore URIs
   /// from file paths.
-  SourceFactory _sourceFactory;
+  final SourceFactory _sourceFactory;
 
   final MacroKernelBuilder? macroKernelBuilder;
 
@@ -277,6 +279,9 @@
     analysisContext?.driver = this;
     _onResults = _resultController.stream.asBroadcastStream();
     _testView = AnalysisDriverTestView(this);
+
+    _fileContentStrategy = StoredFileContentStrategy(_fileContentCache);
+
     _createFileTracker();
     _scheduler.add(this);
     _search = Search(this);
@@ -568,42 +573,6 @@
     _libraryContext = null;
   }
 
-  /// Some state on which analysis depends has changed, so the driver needs to be
-  /// re-configured with the new state.
-  ///
-  /// At least one of the optional parameters should be provided, but only those
-  /// that represent state that has actually changed need be provided.
-  @Deprecated('Provide all necessary values to the constructor')
-  void configure({
-    DriverBasedAnalysisContext? analysisContext,
-    AnalysisOptionsImpl? analysisOptions,
-    Packages? packages,
-    SourceFactory? sourceFactory,
-  }) {
-    if (analysisContext != null) {
-      this.analysisContext = analysisContext;
-      _scheduler.driverWatcher?.addedDriver(this);
-    }
-    if (analysisOptions != null) {
-      _analysisOptions = analysisOptions;
-    }
-    if (packages != null) {
-      _packages = packages;
-    }
-    if (sourceFactory != null) {
-      _sourceFactory = sourceFactory;
-    }
-
-    _priorityResults.clear();
-    clearLibraryContext();
-
-    Iterable<String> addedFiles = _fileTracker.addedFiles;
-    _createFileTracker();
-    _fileTracker.addFiles(addedFiles);
-
-    _scheduler.notify(this);
-  }
-
   /// Return a [Future] that completes when discovery of all files that are
   /// potentially available is done, so that they are included in [knownFiles].
   Future<void> discoverAvailableFiles() {
@@ -1507,9 +1476,11 @@
       _saltForUnlinked,
       _saltForElements,
       featureSetProvider,
-      fileContentCache: _fileContentCache,
+      fileContentStrategy: _fileContentStrategy,
+      prefetchFiles: null,
+      isGenerated: (_) => false,
     );
-    _fileTracker = FileTracker(_logger, _fsState);
+    _fileTracker = FileTracker(_logger, _fsState, _fileContentStrategy);
   }
 
   /// If this has not been done yet, schedule discovery of all files that are
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 193ed91..8ce8e30 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -30,11 +30,14 @@
 import 'package:analyzer/src/generated/parser.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/utilities_dart.dart';
+import 'package:analyzer/src/ignore_comments/ignore_info.dart';
 import 'package:analyzer/src/source/source_resource.dart';
 import 'package:analyzer/src/summary/api_signature.dart';
 import 'package:analyzer/src/summary/package_bundle_reader.dart';
 import 'package:analyzer/src/summary2/informative_data.dart';
 import 'package:analyzer/src/util/either.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
+import 'package:analyzer/src/util/performance/operation_performance.dart';
 import 'package:analyzer/src/util/uri.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:collection/collection.dart';
@@ -186,6 +189,12 @@
   ExternalLibrary._(this.source);
 }
 
+abstract class FileContent {
+  String get content;
+  String get contentHash;
+  bool get exists;
+}
+
 /// [FileContentOverlay] is used to temporary override content of files.
 class FileContentOverlay {
   final _map = <String, String>{};
@@ -211,6 +220,10 @@
   }
 }
 
+abstract class FileContentStrategy {
+  FileContent get(String path);
+}
+
 /// Information about a file being analyzed, explicitly or implicitly.
 ///
 /// It provides a consistent view on its properties.
@@ -251,9 +264,7 @@
   /// The language version for the package that contains this file.
   final Version packageLanguageVersion;
 
-  bool? _exists;
-  String? _content;
-  String? _contentHash;
+  FileContent? _fileContent;
   LineInfo? _lineInfo;
   Uint8List? _unlinkedSignature;
   String? _unlinkedKey;
@@ -306,10 +317,10 @@
   }
 
   /// The content of the file.
-  String get content => _content!;
+  String get content => _fileContent!.content;
 
   /// The MD5 hash of the [content].
-  String get contentHash => _contentHash!;
+  String get contentHash => _fileContent!.contentHash;
 
   /// The class member names defined by the file.
   Set<String> get definedClassMemberNames {
@@ -341,7 +352,7 @@
   }
 
   /// Return `true` if the file exists.
-  bool get exists => _exists!;
+  bool get exists => _fileContent!.exists;
 
   /// The list of files this file exports.
   List<FileState?> get exportedFiles {
@@ -417,6 +428,10 @@
     return _driverUnlinkedUnit!.referencedNames;
   }
 
+  File get resource {
+    return _fsState._resourceProvider.getFile(path);
+  }
+
   @visibleForTesting
   FileStateTestView get test => FileStateTestView(this);
 
@@ -448,6 +463,8 @@
   /// The [UnlinkedUnit] of the file.
   UnlinkedUnit get unlinked2 => _unlinked2!;
 
+  String get unlinkedKey => _unlinkedKey!;
+
   /// The MD5 signature based on the content, feature sets, language version.
   Uint8List get unlinkedSignature => _unlinkedSignature!;
 
@@ -459,6 +476,26 @@
     return other is FileState && other.uri == uri;
   }
 
+  /// Collect all files that are transitively referenced by this file via
+  /// imports, exports, and parts.
+  void collectAllReferencedFiles(Set<String> referencedFiles) {
+    for (final file in directReferencedFiles) {
+      if (referencedFiles.add(file.path)) {
+        file.collectAllReferencedFiles(referencedFiles);
+      }
+    }
+  }
+
+  /// Return the content of the file, the empty string if cannot be read.
+  ///
+  /// We read the file digest, end verify that it is the same as the digest
+  /// that was recorded during the file creation. If it is not, then the file
+  /// was changed, and we failed to call [FileSystemState.changeFile].
+  /// TODO(scheglov) replace with [content]
+  String getContent() {
+    return _fileContent!.content;
+  }
+
   void internal_setLibraryCycle(LibraryCycle? cycle) {
     _libraryCycle = cycle;
   }
@@ -482,6 +519,43 @@
     }
   }
 
+  /// TODO(scheglov) Remove it when [IgnoreInfo] is stored here.
+  CompilationUnitImpl parse2(
+    AnalysisErrorListener errorListener,
+    String content,
+  ) {
+    CharSequenceReader reader = CharSequenceReader(content);
+    Scanner scanner = Scanner(source, reader, errorListener)
+      ..configureFeatures(
+        featureSetForOverriding: _contextFeatureSet,
+        featureSet: _contextFeatureSet.restrictToVersion(
+          packageLanguageVersion,
+        ),
+      );
+    Token token = scanner.tokenize(reportScannerErrors: false);
+    LineInfo lineInfo = LineInfo(scanner.lineStarts);
+
+    Parser parser = Parser(
+      source,
+      errorListener,
+      featureSet: scanner.featureSet,
+      lineInfo: lineInfo,
+    );
+    parser.enableOptionalNewAndConst = true;
+
+    var unit = parser.parseCompilationUnit(token);
+    unit.languageVersion = LibraryLanguageVersion(
+      package: packageLanguageVersion,
+      override: scanner.overrideVersion,
+    );
+
+    // StringToken uses a static instance of StringCanonicalizer, so we need
+    // to clear it explicitly once we are done using it for this file.
+    StringTokenImpl.canonicalizer.clear();
+
+    return unit;
+  }
+
   /// Read the file content and ensure that all of the file properties are
   /// consistent with the read content, including API signature.
   ///
@@ -489,11 +563,10 @@
   FileStateRefreshResult refresh() {
     _invalidateCurrentUnresolvedData();
 
-    final rawFileState = _fsState._fileContentCache.get(path);
-    final contentChanged = _contentHash != rawFileState.contentHash;
-    _content = rawFileState.content;
-    _exists = rawFileState.exists;
-    _contentHash = rawFileState.contentHash;
+    final rawFileState = _fsState.fileContentStrategy.get(path);
+    final contentChanged =
+        _fileContent?.contentHash != rawFileState.contentHash;
+    _fileContent = rawFileState;
 
     // Prepare the unlinked bundle key.
     {
@@ -501,8 +574,8 @@
       signature.addUint32List(_fsState._saltForUnlinked);
       signature.addFeatureSet(_contextFeatureSet);
       signature.addLanguageVersion(packageLanguageVersion);
-      signature.addString(_contentHash!);
-      signature.addBool(_exists!);
+      signature.addString(contentHash);
+      signature.addBool(exists);
       _unlinkedSignature = signature.toByteList();
       var signatureHex = hex.encode(_unlinkedSignature!);
       // TODO(scheglov) Use the path as the key, and store the signature.
@@ -514,6 +587,8 @@
     _unlinked2 = _driverUnlinkedUnit!.unit;
     _lineInfo = LineInfo(_unlinked2!.lineStarts);
 
+    _prefetchDirectReferences();
+
     // Prepare API signature.
     var newApiSignature = _unlinked2!.apiSignature;
     bool apiSignatureChanged = _apiSignature != null &&
@@ -588,8 +663,11 @@
 
   /// Return the unlinked unit, from bytes or new.
   AnalysisDriverUnlinkedUnit _getUnlinkedUnit() {
+    final testData = _fsState.testData?.forFile(resource);
+
     var bytes = _fsState._byteStore.get(_unlinkedKey!);
     if (bytes != null && bytes.isNotEmpty) {
+      testData?.unlinkedKeyGet.add(unlinkedKey);
       return AnalysisDriverUnlinkedUnit.fromBytes(bytes);
     }
 
@@ -608,6 +686,7 @@
       );
       var bytes = driverUnlinkedUnit.toBytes();
       _fsState._byteStore.putGet(_unlinkedKey!, bytes);
+      testData?.unlinkedKeyPut.add(unlinkedKey);
       return driverUnlinkedUnit;
     });
   }
@@ -637,36 +716,43 @@
   }
 
   CompilationUnitImpl _parse(AnalysisErrorListener errorListener) {
-    CharSequenceReader reader = CharSequenceReader(content);
-    Scanner scanner = Scanner(source, reader, errorListener)
-      ..configureFeatures(
-        featureSetForOverriding: _contextFeatureSet,
-        featureSet: _contextFeatureSet.restrictToVersion(
-          packageLanguageVersion,
-        ),
-      );
-    Token token = scanner.tokenize(reportScannerErrors: false);
-    LineInfo lineInfo = LineInfo(scanner.lineStarts);
+    return parse2(errorListener, content);
+  }
 
-    Parser parser = Parser(
-      source,
-      errorListener,
-      featureSet: scanner.featureSet,
-      lineInfo: lineInfo,
-    );
-    parser.enableOptionalNewAndConst = true;
+  /// TODO(scheglov) write tests
+  void _prefetchDirectReferences() {
+    final prefetchFiles = _fsState.prefetchFiles;
+    if (prefetchFiles == null) {
+      return;
+    }
 
-    var unit = parser.parseCompilationUnit(token);
-    unit.languageVersion = LibraryLanguageVersion(
-      package: packageLanguageVersion,
-      override: scanner.overrideVersion,
-    );
+    var paths = <String>{};
 
-    // StringToken uses a static instance of StringCanonicalizer, so we need
-    // to clear it explicitly once we are done using it for this file.
-    StringTokenImpl.canonicalizer.clear();
+    void addRelativeUri(String relativeUriStr) {
+      final Uri absoluteUri;
+      try {
+        final relativeUri = Uri.parse(relativeUriStr);
+        absoluteUri = resolveRelativeUri(uri, relativeUri);
+      } on FormatException {
+        return;
+      }
+      final path = _fsState._sourceFactory.forUri2(absoluteUri)?.fullName;
+      if (path != null) {
+        paths.add(path);
+      }
+    }
 
-    return unit;
+    for (final directive in unlinked2.imports) {
+      addRelativeUri(directive.uri);
+    }
+    for (final directive in unlinked2.exports) {
+      addRelativeUri(directive.uri);
+    }
+    for (final uri in unlinked2.parts) {
+      addRelativeUri(uri);
+    }
+
+    prefetchFiles(paths.toList());
   }
 
   /// TODO(scheglov) move to _fsState?
@@ -893,6 +979,29 @@
         ),
       );
     }
+
+    final topLevelDeclarations = <String>{};
+    for (final declaration in unit.declarations) {
+      if (declaration is ClassDeclaration) {
+        topLevelDeclarations.add(declaration.name.name);
+      } else if (declaration is EnumDeclaration) {
+        topLevelDeclarations.add(declaration.name.name);
+      } else if (declaration is ExtensionDeclaration) {
+        var name = declaration.name;
+        if (name != null) {
+          topLevelDeclarations.add(name.name);
+        }
+      } else if (declaration is FunctionDeclaration) {
+        topLevelDeclarations.add(declaration.name.name);
+      } else if (declaration is MixinDeclaration) {
+        topLevelDeclarations.add(declaration.name.name);
+      } else if (declaration is TopLevelVariableDeclaration) {
+        for (var variable in declaration.variables.variables) {
+          topLevelDeclarations.add(variable.name.name);
+        }
+      }
+    }
+
     return UnlinkedUnit(
       apiSignature: Uint8List.fromList(computeUnlinkedApiSignature(unit)),
       augmentations: augmentations,
@@ -906,6 +1015,7 @@
       parts: parts,
       partOfNameDirective: partOfNameDirective,
       partOfUriDirective: partOfUriDirective,
+      topLevelDeclarations: topLevelDeclarations,
     );
   }
 
@@ -1022,12 +1132,20 @@
   /// The value of this field is incremented when the set of files is updated.
   int fileStamp = 0;
 
-  /// The cache of content of files, possibly shared with other file system
-  /// states.
-  final FileContentCache _fileContentCache;
+  final FileContentStrategy fileContentStrategy;
+
+  /// A function that fetches the given list of files. This function can be used
+  /// to batch file reads in systems where file fetches are expensive.
+  final void Function(List<String> paths)? prefetchFiles;
+
+  /// A function that returns true if the given file path is likely to be that
+  /// of a file that is generated.
+  final bool Function(String path) isGenerated;
 
   late final FileSystemStateTestView _testView;
 
+  FileSystemTestData? testData;
+
   FileSystemState(
     this._logger,
     this._byteStore,
@@ -1041,8 +1159,10 @@
     this._saltForUnlinked,
     this._saltForElements,
     this.featureSetProvider, {
-    required FileContentCache fileContentCache,
-  }) : _fileContentCache = fileContentCache {
+    required this.fileContentStrategy,
+    required this.prefetchFiles,
+    required this.isGenerated,
+  }) {
     _testView = FileSystemStateTestView(this);
   }
 
@@ -1051,6 +1171,29 @@
   @visibleForTesting
   FileSystemStateTestView get test => _testView;
 
+  /// Update the state to reflect the fact that the file with the given [path]
+  /// was changed. Specifically this means that we evict this file and every
+  /// file that referenced it.
+  void changeFile(String path, List<FileState> removedFiles) {
+    var file = _pathToFile.remove(path);
+    if (file == null) {
+      return;
+    }
+
+    removedFiles.add(file);
+    _uriToFile.remove(file.uri);
+
+    // The removed file does not reference other file anymore.
+    for (var referencedFile in file.directReferencedFiles) {
+      referencedFile.referencingFiles.remove(file);
+    }
+
+    // Recursively remove files that reference the removed file.
+    for (var reference in file.referencingFiles.toList()) {
+      changeFile(reference.path, removedFiles);
+    }
+  }
+
   /// Collected files that transitively reference a file with the [path].
   /// These files are potentially affected by the change.
   void collectAffected(String path, Set<FileState> affected) {
@@ -1104,9 +1247,40 @@
     return featureSetProvider.getLanguageVersion(path, uri);
   }
 
+  /// Notifies this object that it is about to be discarded.
+  ///
+  /// Returns the keys of the artifacts that are no longer used.
+  Set<String> dispose() {
+    final result = <String>{};
+    for (final file in _pathToFile.values) {
+      result.add(file._unlinkedKey!);
+    }
+    _pathToFile.clear();
+    _uriToFile.clear();
+    return result;
+  }
+
+  @visibleForTesting
+  FileState? getExistingFileForResource(File file) {
+    return _pathToFile[file.path];
+  }
+
   /// Return the [FileState] for the given absolute [path]. The returned file
   /// has the last known state since if was last refreshed.
+  /// TODO(scheglov) Merge with [getFileForPath2].
   FileState getFileForPath(String path) {
+    return getFileForPath2(
+      path: path,
+      performance: OperationPerformanceImpl('<root>'),
+    );
+  }
+
+  /// Return the [FileState] for the given absolute [path]. The returned file
+  /// has the last known state since if was last refreshed.
+  FileState getFileForPath2({
+    required String path,
+    required OperationPerformanceImpl performance,
+  }) {
     var file = _pathToFile[path];
     if (file == null) {
       File resource = _resourceProvider.getFile(path);
@@ -1164,12 +1338,38 @@
     return Either2.t1(file);
   }
 
+  /// Returns a list of files whose contents contains the given string.
+  /// Generated files are not included in the search.
+  List<String> getFilesContaining(String value) {
+    var result = <String>[];
+    _pathToFile.forEach((path, file) {
+      // TODO(scheglov) tests for excluding generated
+      if (!isGenerated(path)) {
+        if (file.getContent().contains(value)) {
+          result.add(path);
+        }
+      }
+    });
+    return result;
+  }
+
   /// Return files where the given [name] is subtyped, i.e. used in `extends`,
   /// `with` or `implements` clauses.
   Set<FileState>? getFilesSubtypingName(String name) {
     return _subtypedNameToFiles[name];
   }
 
+  /// Return files that have a top-level declaration with the [name].
+  List<FileState> getFilesWithTopLevelDeclarations(String name) {
+    final result = <FileState>[];
+    for (final file in _pathToFile.values) {
+      if (file.unlinked2.topLevelDeclarations.contains(name)) {
+        result.add(file);
+      }
+    }
+    return result;
+  }
+
   /// Return `true` if there is a URI that can be resolved to the [path].
   ///
   /// When a file exists, but for the URI that corresponds to the file is
@@ -1186,18 +1386,34 @@
     return flag;
   }
 
-  /// The file with the given [path] might have changed, so ensure that it is
-  /// read the next time it is refreshed.
-  void markFileForReading(String path) {
-    _fileContentCache.invalidate(path);
-  }
-
   /// Remove the file with the given [path].
   void removeFile(String path) {
-    markFileForReading(path);
     _clearFiles();
   }
 
+  /// Computes the set of [FileState]'s used/not used to analyze the given
+  /// [files]. Removes the [FileState]'s of the files not used for analysis from
+  /// the cache. Returns the set of unused [FileState]'s.
+  List<FileState> removeUnusedFiles(List<String> files) {
+    var allReferenced = <String>{};
+    for (var path in files) {
+      allReferenced.add(path);
+      _pathToFile[path]?.collectAllReferencedFiles(allReferenced);
+    }
+
+    var unusedPaths = _pathToFile.keys.toSet();
+    unusedPaths.removeAll(allReferenced);
+
+    var removedFiles = <FileState>[];
+    for (var path in unusedPaths) {
+      var file = _pathToFile.remove(path)!;
+      _uriToFile.remove(file.uri);
+      removedFiles.add(file);
+    }
+
+    return removedFiles;
+  }
+
   /// Clear all [FileState] data - all maps from path or URI, etc.
   void _clearFiles() {
     _uriToFile.clear();
@@ -1240,6 +1456,26 @@
   }
 }
 
+class FileSystemTestData {
+  final Map<File, FileTestData> files = {};
+
+  FileTestData forFile(File file) {
+    return files[file] ??= FileTestData._(file);
+  }
+}
+
+class FileTestData {
+  final File file;
+
+  /// We add the key every time we get unlinked data from the byte store.
+  final List<String> unlinkedKeyGet = [];
+
+  /// We add the key every time we put unlinked data into the byte store.
+  final List<String> unlinkedKeyPut = [];
+
+  FileTestData._(this.file);
+}
+
 /// Precomputed properties of a file URI, used because [Uri] is relatively
 /// expensive to work with, if we do this thousand times.
 class FileUriProperties {
@@ -1471,6 +1707,8 @@
   /// first one as if sorted by path.
   @override
   LibraryFileStateKind? get library {
+    _discoverLibraries();
+
     LibraryFileStateKind? result;
     for (final library in libraries) {
       if (library.hasPart(this)) {
@@ -1483,6 +1721,27 @@
     }
     return result;
   }
+
+  void _discoverLibraries() {
+    if (libraries.isEmpty) {
+      var resourceProvider = file._fsState._resourceProvider;
+      var pathContext = resourceProvider.pathContext;
+
+      var siblings = <Resource>[];
+      try {
+        siblings = file.resource.parent.getChildren();
+      } catch (_) {}
+
+      for (final sibling in siblings) {
+        if (file_paths.isDart(pathContext, sibling.path)) {
+          file._fsState.getFileForPath2(
+            path: sibling.path,
+            performance: OperationPerformanceImpl('<root>'),
+          );
+        }
+      }
+    }
+  }
 }
 
 /// The file has `part of URI` directive.
@@ -1528,6 +1787,45 @@
   LibraryFileStateKind? get library => null;
 }
 
+class StoredFileContent implements FileContent {
+  @override
+  final String content;
+
+  @override
+  final String contentHash;
+
+  @override
+  final bool exists;
+
+  StoredFileContent({
+    required this.content,
+    required this.contentHash,
+    required this.exists,
+  });
+}
+
+class StoredFileContentStrategy implements FileContentStrategy {
+  final FileContentCache _fileContentCache;
+
+  StoredFileContentStrategy(this._fileContentCache);
+
+  @override
+  FileContent get(String path) {
+    final fileContent = _fileContentCache.get(path);
+    return StoredFileContent(
+      content: fileContent.content,
+      contentHash: fileContent.contentHash,
+      exists: fileContent.exists,
+    );
+  }
+
+  /// The file with the given [path] might have changed, so ensure that it is
+  /// read the next time it is refreshed.
+  void markFileForReading(String path) {
+    _fileContentCache.invalidate(path);
+  }
+}
+
 class _LibraryNameToFiles {
   final Map<String, List<LibraryFileStateKind>> _map = {};
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart b/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart
index 7371c3f..e1606ce 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart
@@ -22,6 +22,8 @@
   /// The current file system state.
   final FileSystemState _fsState;
 
+  final StoredFileContentStrategy _fileContentStrategy;
+
   /// The set of added files.
   final addedFiles = <String>{};
 
@@ -45,7 +47,7 @@
   /// have any special relation with changed files.
   var _pendingFiles = <String>{};
 
-  FileTracker(this._logger, this._fsState);
+  FileTracker(this._logger, this._fsState, this._fileContentStrategy);
 
   /// Returns the path to exactly one that needs analysis.  Throws a
   /// [StateError] if no files need analysis.
@@ -108,7 +110,7 @@
 
   /// Adds the given [path] to the set of "changed files".
   void changeFile(String path) {
-    _fsState.markFileForReading(path);
+    _fileContentStrategy.markFileForReading(path);
     _changedFiles.add(path);
 
     if (addedFiles.contains(path)) {
@@ -137,6 +139,7 @@
 
   /// Removes the given [path] from the set of "added files".
   void removeFile(String path) {
+    _fileContentStrategy.markFileForReading(path);
     addedFiles.remove(path);
     _pendingChangedFiles.remove(path);
     _pendingImportFiles.remove(path);
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
index 95957aa..89f3f5f 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
@@ -52,6 +52,10 @@
   /// include [implSignature] of the macro defining library.
   String implSignature;
 
+  /// The key of the resolution cache entry.
+  /// TODO(scheglov) clean up
+  String? resolutionKey;
+
   late final bool hasMacroClass = () {
     for (final library in libraries) {
       for (final file in library.libraryFiles) {
diff --git a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart
index d9a7714..18152a3 100644
--- a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart
@@ -343,6 +343,9 @@
   /// The `part of 'uri';` directive.
   final UnlinkedPartOfUriDirective? partOfUriDirective;
 
+  /// Top-level declarations of the unit.
+  final Set<String> topLevelDeclarations;
+
   UnlinkedUnit({
     required this.apiSignature,
     required this.augmentations,
@@ -356,6 +359,7 @@
     required this.parts,
     required this.partOfNameDirective,
     required this.partOfUriDirective,
+    required this.topLevelDeclarations,
   });
 
   factory UnlinkedUnit.read(SummaryDataReader reader) {
@@ -388,6 +392,7 @@
       partOfUriDirective: reader.readOptionalObject(
         UnlinkedPartOfUriDirective.read,
       ),
+      topLevelDeclarations: reader.readStringUtf8Set(),
     );
   }
 
@@ -424,5 +429,6 @@
       partOfUriDirective,
       (x) => x.write(sink),
     );
+    sink.writeStringUtf8Iterable(topLevelDeclarations);
   }
 }
diff --git a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
index f02eacb..f81ea9f 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
@@ -9,6 +9,7 @@
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/context/source.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/dart/constant/compute.dart';
@@ -19,7 +20,6 @@
 import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
 import 'package:analyzer/src/dart/element/type_provider.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
-import 'package:analyzer/src/dart/micro/library_graph.dart';
 import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
 import 'package:analyzer/src/dart/resolver/legacy_type_asserter.dart';
 import 'package:analyzer/src/dart/resolver/resolution_visitor.dart';
@@ -106,7 +106,8 @@
 
     // Parse all files.
     performance.run('parse', (performance) {
-      for (FileState file in _library.files().ofLibrary) {
+      final libraryKind = _library.kind.asLibrary;
+      for (FileState file in libraryKind.file.libraryFiles) {
         if (completionPath == null || file.path == completionPath) {
           units[file] = _parse(
             file: file,
@@ -227,21 +228,24 @@
       });
     }
 
+    final libraryKind = _library.kind.asLibrary;
+    final libraryFiles = libraryKind.file.libraryFiles.toList();
+
     if (_analysisOptions.lint) {
       performance.run('computeLints', (performance) {
-        var allUnits = _library.files().ofLibrary.map((file) {
+        var allUnits = libraryFiles.map((file) {
           var content = getFileContent(file);
           return LinterContextUnit(content, units[file]!);
         }).toList();
         for (int i = 0; i < allUnits.length; i++) {
-          _computeLints(_library.files().ofLibrary[i], allUnits[i], allUnits);
+          _computeLints(libraryFiles[i], allUnits[i], allUnits);
         }
       });
     }
 
     // This must happen after all other diagnostics have been computed but
     // before the list of diagnostics has been filtered.
-    for (var file in _library.files().ofLibrary) {
+    for (var file in libraryFiles) {
       IgnoreValidator(
         _getErrorReporter(file),
         _getErrorListener(file).errors,
@@ -475,7 +479,8 @@
   }
 
   bool _isExistingSource(Source source) {
-    for (var file in _library.files().directReferencedFiles) {
+    final libraryKind = _library.kind.asLibrary;
+    for (var file in libraryKind.file.directReferencedFiles) {
       if (file.uri == source.uri) {
         return file.exists;
       }
@@ -499,7 +504,7 @@
     performance.getDataInt('length').add(content.length);
 
     AnalysisErrorListener errorListener = _getErrorListener(file);
-    var unit = file.parse(errorListener, content);
+    var unit = file.parse2(errorListener, content);
 
     LineInfo lineInfo = unit.lineInfo;
     _fileToLineInfo[file] = lineInfo;
@@ -544,6 +549,8 @@
       return;
     }
 
+    final libraryKind = _library.kind.asLibrary;
+
     var definingCompilationUnit = units[_library]!;
     definingCompilationUnit.element = _libraryElement.definingCompilationUnit;
 
@@ -598,7 +605,7 @@
       } else if (directive is PartDirectiveImpl) {
         StringLiteral partUri = directive.uri;
 
-        FileState partFile = _library.files().parted[partIndex];
+        FileState partFile = libraryKind.file.partedFiles[partIndex]!;
         var partUnit = units[partFile]!;
         CompilationUnitElement partElement = _libraryElement.parts[partIndex];
         partUnit.element = partElement;
diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
index 12c250f..c182ed9 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
@@ -1045,6 +1045,7 @@
       parts: parts,
       partOfNameDirective: partOfNameDirective,
       partOfUriDirective: partOfUriDirective,
+      topLevelDeclarations: topLevelDeclarations,
     );
 
     return CiderUnlinkedUnit(
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index 92f57a8..cb43de1 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -5,6 +5,7 @@
 import 'dart:collection';
 import 'dart:typed_data';
 
+import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/error/error.dart';
@@ -19,14 +20,14 @@
 import 'package:analyzer/src/dart/analysis/driver.dart' show ErrorEncoding;
 import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/library_graph.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:analyzer/src/dart/analysis/results.dart';
 import 'package:analyzer/src/dart/analysis/search.dart';
 import 'package:analyzer/src/dart/micro/analysis_context.dart';
 import 'package:analyzer/src/dart/micro/library_analyzer.dart';
-import 'package:analyzer/src/dart/micro/library_graph.dart';
 import 'package:analyzer/src/dart/micro/utils.dart';
-import 'package:analyzer/src/exception/exception.dart';
 import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/summary/api_signature.dart';
@@ -43,6 +44,75 @@
 import 'package:meta/meta.dart';
 import 'package:yaml/yaml.dart';
 
+class CiderFileContent implements FileContent {
+  final CiderFileContentStrategy strategy;
+  final String path;
+  final String digestStr;
+
+  CiderFileContent({
+    required this.strategy,
+    required this.path,
+    required this.digestStr,
+  });
+
+  @override
+  String get content {
+    final contentWithDigest = _getContent();
+
+    if (contentWithDigest.digestStr != digestStr) {
+      throw StateError('File was changed, but not invalidated: $path');
+    }
+
+    return contentWithDigest.content;
+  }
+
+  @override
+  String get contentHash => digestStr;
+
+  @override
+  bool get exists => digestStr.isNotEmpty;
+
+  _ContentWithDigest _getContent() {
+    String content;
+    try {
+      final file = strategy.resourceProvider.getFile(path);
+      content = file.readAsStringSync();
+    } catch (_) {
+      content = '';
+    }
+
+    final digestStr = strategy.getFileDigest(path);
+    return _ContentWithDigest(
+      content: content,
+      digestStr: digestStr,
+    );
+  }
+}
+
+class CiderFileContentStrategy implements FileContentStrategy {
+  final ResourceProvider resourceProvider;
+
+  /// A function that returns the digest for a file as a String. The function
+  /// returns a non null value, returns an empty string if file does
+  /// not exist/has no contents.
+  final String Function(String path) getFileDigest;
+
+  CiderFileContentStrategy({
+    required this.resourceProvider,
+    required this.getFileDigest,
+  });
+
+  @override
+  CiderFileContent get(String path) {
+    final digestStr = getFileDigest(path);
+    return CiderFileContent(
+      strategy: this,
+      path: path,
+      digestStr: digestStr,
+    );
+  }
+}
+
 class CiderSearchInfo {
   final CharacterLocation startPosition;
   final int length;
@@ -97,7 +167,7 @@
 
   /// A function that returns true if the given file path is likely to be that
   /// of a file that is generated.
-  final bool Function(String path)? isGenerated;
+  final bool Function(String path) isGenerated;
 
   /// A function that fetches the given list of files. This function can be used
   /// to batch file reads in systems where file fetches are expensive.
@@ -138,38 +208,15 @@
     required this.getFileDigest,
     required this.prefetchFiles,
     required this.workspace,
-    this.isGenerated,
+    required this.isGenerated,
     required this.byteStore,
   });
 
-  @Deprecated('Use the unnamed constructor instead')
-  FileResolver.from({
-    required this.logger,
-    required this.resourceProvider,
-    required this.sourceFactory,
-    required this.getFileDigest,
-    required this.prefetchFiles,
-    required this.workspace,
-    this.isGenerated,
-    required this.byteStore,
-  });
-
-  /// Update the resolver to reflect the fact that the file with the given
-  /// [path] was changed. We need to make sure that when this file, of any file
-  /// that directly or indirectly referenced it, is resolved, we used the new
-  /// state of the file. Updates [removedCacheKeys] with the ids of the invalidated
-  /// items, used in [releaseAndClearRemovedIds] to release the cache items.
-  /// TODO(scheglov) Remove [releaseKeys] when removing [changeFile].
-  @Deprecated('Use changeFiles() instead')
-  void changeFile(String path) {
-    changeFiles([path], releaseKeys: false);
-  }
-
   /// Update the resolver to reflect the fact that the files with the given
   /// [paths] were changed. For each specified file we need to make sure that
   /// when the file, of any file that directly or indirectly referenced it,
   /// is resolved, we use the new state of the file.
-  void changeFiles(List<String> paths, {bool releaseKeys = true}) {
+  void changeFiles(List<String> paths) {
     if (fsState == null) {
       return;
     }
@@ -192,9 +239,7 @@
     // If we need these libraries later, we will relink and reattach them.
     libraryContext?.remove(removedFiles, removedCacheKeys);
 
-    if (releaseKeys) {
-      releaseAndClearRemovedIds();
-    }
+    releaseAndClearRemovedIds();
   }
 
   /// Collects all the cached artifacts and add all the cache id's for the
@@ -276,8 +321,8 @@
       var file = fileContext.file;
 
       final errorsSignatureBuilder = ApiSignature();
-      errorsSignatureBuilder.addBytes(file.libraryCycle.signature);
-      errorsSignatureBuilder.addBytes(file.digest);
+      errorsSignatureBuilder.addString(file.libraryCycle.apiSignature);
+      errorsSignatureBuilder.addString(file.contentHash);
       final errorsKey = '${errorsSignatureBuilder.toHex()}.errors';
 
       final List<AnalysisError> errors;
@@ -330,7 +375,7 @@
       });
 
       var file = performance.run('fileForPath', (performance) {
-        return fsState!.getFileForPath(
+        return fsState!.getFileForPath2(
           path: path,
           performance: performance,
         );
@@ -368,7 +413,8 @@
     );
     var file = fileContext.file;
 
-    if (file.partOfLibrary != null) {
+    final kind = file.kind;
+    if (kind is! LibraryFileStateKind) {
       throw ArgumentError('$uri is not a library.');
     }
 
@@ -388,12 +434,12 @@
   }) {
     _throwIfNotAbsoluteNormalizedPath(path);
 
-    var file = fsState!.getFileForPath(
+    var file = fsState!.getFileForPath2(
       path: path,
       performance: performance,
     );
 
-    return file.libraryCycle.signatureStr;
+    return file.libraryCycle.apiSignature;
   }
 
   /// Ensure that libraries necessary for resolving [path] are linked.
@@ -427,7 +473,7 @@
       performance: performance,
     );
     var file = fileContext.file;
-    var libraryFile = file.partOfLibrary ?? file;
+    var libraryFile = file.kind.library!.file;
 
     // Load the library, link if necessary.
     await libraryContext!.load(
@@ -486,15 +532,17 @@
       );
       var file = fileContext.file;
 
-      // If we have a `part of` directive, we want to analyze this library.
-      // But the library must include the file, so have its element.
-      var libraryFile = file;
-      var partOfLibrary = file.partOfLibrary;
-      if (partOfLibrary != null) {
-        if (partOfLibrary.files().ofLibrary.contains(file)) {
-          libraryFile = partOfLibrary;
-        }
-      }
+      // // If we have a `part of` directive, we want to analyze this library.
+      // // But the library must include the file, so have its element.
+      // var libraryFile = file;
+      // var partOfLibrary = file.partOfLibrary;
+      // if (partOfLibrary != null) {
+      //   if (partOfLibrary.files().ofLibrary.contains(file)) {
+      //     libraryFile = partOfLibrary;
+      //   }
+      // }
+      final libraryKind = file.kind.library ?? file.kind.asLibrary;
+      final libraryFile = libraryKind.file;
 
       var libraryResult = await resolveLibrary2(
         completionLine: completionLine,
@@ -533,15 +581,17 @@
       );
       var file = fileContext.file;
 
-      // If we have a `part of` directive, we want to analyze this library.
-      // But the library must include the file, so have its element.
-      var libraryFile = file;
-      var partOfLibrary = file.partOfLibrary;
-      if (partOfLibrary != null) {
-        if (partOfLibrary.files().ofLibrary.contains(file)) {
-          libraryFile = partOfLibrary;
-        }
-      }
+      // // If we have a `part of` directive, we want to analyze this library.
+      // // But the library must include the file, so have its element.
+      // var libraryFile = file;
+      // var partOfLibrary = file.partOfLibrary;
+      // if (partOfLibrary != null) {
+      //   if (partOfLibrary.files().ofLibrary.contains(file)) {
+      //     libraryFile = partOfLibrary;
+      //   }
+      // }
+      final libraryKind = file.kind.library ?? file.kind.asLibrary;
+      final libraryFile = libraryKind.file;
 
       int? completionOffset;
       if (completionLine != null && completionColumn != null) {
@@ -573,26 +623,13 @@
           (file) => file.getContent(),
         );
 
-        try {
-          results = performance!.run('analyze', (performance) {
-            return libraryAnalyzer.analyze(
-              completionPath: completionOffset != null ? completionPath : null,
-              completionOffset: completionOffset,
-              performance: performance,
-            );
-          });
-        } catch (exception, stackTrace) {
-          var fileContentMap = <String, String>{};
-          for (var file in libraryFile.files().ofLibrary) {
-            var path = file.path;
-            fileContentMap[path] = _getFileContent(path);
-          }
-          throw CaughtExceptionWithFiles(
-            exception,
-            stackTrace,
-            fileContentMap,
+        results = performance!.run('analyze', (performance) {
+          return libraryAnalyzer.analyze(
+            completionPath: completionOffset != null ? completionPath : null,
+            completionOffset: completionOffset,
+            performance: performance,
           );
-        }
+        });
       });
 
       var resolvedUnits = results.values.map((fileResult) {
@@ -655,15 +692,23 @@
       );
 
       fsState = FileSystemState(
-        resourceProvider,
+        logger,
         byteStore,
+        resourceProvider,
+        'contextName',
         sourceFactory,
         workspace,
-        Uint32List(0), // linkedSalt
+        AnalysisOptionsImpl(), // TODO(scheglov) remove it
+        DeclaredVariables.fromMap({}),
+        Uint32List(0), // _saltForUnlinked
+        Uint32List(0), // _saltForElements
         featureSetProvider,
-        getFileDigest,
-        prefetchFiles,
-        isGenerated,
+        fileContentStrategy: CiderFileContentStrategy(
+          resourceProvider: resourceProvider,
+          getFileDigest: getFileDigest,
+        ),
+        prefetchFiles: prefetchFiles,
+        isGenerated: isGenerated,
       )..testData = testView?.fileSystemTestData;
     }
 
@@ -779,15 +824,6 @@
     return options;
   }
 
-  /// Return the file content, the empty string if any exception.
-  String _getFileContent(String path) {
-    try {
-      return resourceProvider.getFile(path).readAsStringSync();
-    } catch (_) {
-      return '';
-    }
-  }
-
   Future<List<CiderSearchMatch>> _searchReferences_Import(
       ImportElement element) async {
     var results = <CiderSearchMatch>[];
@@ -897,14 +933,13 @@
         await loadBundle(directDependency);
       }
 
-      var resolutionKey = '${cycle.signatureStr}.resolution';
+      var resolutionKey = '${cycle.apiSignature}.linked_bundle';
       var resolutionBytes = byteStore.get(resolutionKey);
 
       var unitsInformativeBytes = <Uri, Uint8List>{};
       for (var library in cycle.libraries) {
-        for (var file in library.files().ofLibrary) {
-          var informativeBytes = file.unlinkedUnit.informativeBytes;
-          unitsInformativeBytes[file.uri] = informativeBytes;
+        for (var file in library.libraryFiles) {
+          unitsInformativeBytes[file.uri] = file.unlinked2.informativeBytes;
         }
       }
 
@@ -918,21 +953,21 @@
 
           var inputUnits = <LinkInputUnit>[];
           var partIndex = -1;
-          for (var file in libraryFile.files().ofLibrary) {
+          for (var file in libraryFile.libraryFiles) {
             var isSynthetic = !file.exists;
 
             var content = file.getContent();
             performance.getDataInt('parseCount').increment();
             performance.getDataInt('parseLength').add(content.length);
 
-            var unit = file.parse(
+            var unit = file.parse2(
               AnalysisErrorListener.NULL_LISTENER,
               content,
             );
 
             String? partUriStr;
             if (partIndex >= 0) {
-              partUriStr = libraryFile.unlinkedUnit.parts[partIndex];
+              partUriStr = libraryFile.unlinked2.parts[partIndex];
             }
             partIndex++;
 
@@ -1069,3 +1104,13 @@
   final List<String> getKeys = [];
   final List<String> putKeys = [];
 }
+
+class _ContentWithDigest {
+  final String content;
+  final String digestStr;
+
+  _ContentWithDigest({
+    required this.content,
+    required this.digestStr,
+  });
+}
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index ebe16a8..0fc9372 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -70,22 +70,12 @@
   /// context. Clients should not modify the returned set of options.
   AnalysisOptions get analysisOptions;
 
-  /// Set the set of analysis options controlling the behavior of this context to
-  /// the given [options]. Clients can safely assume that all necessary analysis
-  /// results have been invalidated.
-  set analysisOptions(AnalysisOptions options);
-
   /// Return the set of declared variables used when computing constant values.
   DeclaredVariables get declaredVariables;
 
   /// Return the source factory used to create the sources that can be analyzed
   /// in this context.
   SourceFactory get sourceFactory;
-
-  /// Set the source factory used to create the sources that can be analyzed in
-  /// this context to the given source [factory]. Clients can safely assume that
-  /// all analysis results have been invalidated.
-  set sourceFactory(SourceFactory factory);
 }
 
 /// The entry point for the functionality provided by the analysis engine. There
diff --git a/pkg/analyzer/lib/src/services/lint.dart b/pkg/analyzer/lib/src/services/lint.dart
index 49603d43..05dff99 100644
--- a/pkg/analyzer/lib/src/services/lint.dart
+++ b/pkg/analyzer/lib/src/services/lint.dart
@@ -7,7 +7,6 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/dart/error/lint_codes.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/lint/linter.dart';
 
 /// Current linter version.
@@ -16,19 +15,6 @@
 /// Shared lint registry.
 LintRegistry lintRegistry = LintRegistry();
 
-/// Return lints associated with this [context], or an empty list if there are
-/// none.
-List<Linter> getLints(AnalysisContext context) =>
-    context.analysisOptions.lintRules;
-
-/// Associate these [lints] with the given [context].
-void setLints(AnalysisContext context, List<Linter> lints) {
-  AnalysisOptionsImpl options =
-      AnalysisOptionsImpl.from(context.analysisOptions);
-  options.lintRules = lints;
-  context.analysisOptions = options;
-}
-
 /// Implementers contribute lint warnings via the provided error [reporter].
 abstract class Linter implements NodeLintRule {
   /// Used to report lint warnings.
diff --git a/pkg/analyzer/test/source/error_processor_test.dart b/pkg/analyzer/test/source/error_processor_test.dart
index c9ec408..20bbac0 100644
--- a/pkg/analyzer/test/source/error_processor_test.dart
+++ b/pkg/analyzer/test/source/error_processor_test.dart
@@ -2,15 +2,11 @@
 // 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.
 
-import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/source/error_processor.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/context/context.dart';
-import 'package:analyzer/src/dart/analysis/session.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/task/options.dart';
 import 'package:collection/collection.dart';
 import 'package:test/test.dart';
@@ -44,13 +40,15 @@
   AnalysisError annotate_overrides =
       AnalysisError(TestSource(), 0, 1, LintCode('annotate_overrides', ''));
 
-  setUp(() {
-    context = TestContext();
-  });
-
   group('ErrorProcessor', () {
+    late _TestContext context;
+
+    setUp(() {
+      context = _TestContext();
+    });
+
     test('configureOptions', () {
-      configureOptions('''
+      context.configureOptions('''
 analyzer:
   errors:
     invalid_assignment: error # severity ERROR
@@ -58,18 +56,19 @@
     unused_local_variable: true # skipped
     use_of_void_result: unsupported_action # skipped
 ''');
-      expect(getProcessor(invalid_assignment)!.severity, ErrorSeverity.ERROR);
-      expect(getProcessor(missing_return)!.severity, isNull);
-      expect(getProcessor(unused_local_variable), isNull);
-      expect(getProcessor(use_of_void_result), isNull);
+      expect(context.getProcessor(invalid_assignment)!.severity,
+          ErrorSeverity.ERROR);
+      expect(context.getProcessor(missing_return)!.severity, isNull);
+      expect(context.getProcessor(unused_local_variable), isNull);
+      expect(context.getProcessor(use_of_void_result), isNull);
     });
 
     test('does not upgrade other warnings to errors in strong mode', () {
-      configureOptions('''
+      context.configureOptions('''
 analyzer:
   strong-mode: true
 ''');
-      expect(getProcessor(unused_local_variable), isNull);
+      expect(context.getProcessor(unused_local_variable), isNull);
     });
   });
 
@@ -84,7 +83,7 @@
 
     group('processing', () {
       test('yaml map', () {
-        var options = optionsProvider.getOptionsFromString(config);
+        var options = AnalysisOptionsProvider().getOptionsFromString(config);
         var errorConfig =
             ErrorConfig((options['analyzer'] as YamlMap)['errors']);
         expect(errorConfig.processors, hasLength(2));
@@ -132,7 +131,7 @@
     });
 
     test('configure lints', () {
-      var options = optionsProvider.getOptionsFromString(
+      var options = AnalysisOptionsProvider().getOptionsFromString(
           'analyzer:\n  errors:\n    annotate_overrides: warning\n');
       var errorConfig = ErrorConfig((options['analyzer'] as YamlMap)['errors']);
       expect(errorConfig.processors, hasLength(1));
@@ -144,30 +143,15 @@
   });
 }
 
-late TestContext context;
+class _TestContext {
+  final analysisOptions = AnalysisOptionsImpl();
 
-AnalysisOptionsProvider optionsProvider = AnalysisOptionsProvider();
+  void configureOptions(String options) {
+    final optionMap = AnalysisOptionsProvider().getOptionsFromString(options);
+    applyToAnalysisOptions(analysisOptions, optionMap);
+  }
 
-void configureOptions(String options) {
-  YamlMap optionMap = optionsProvider.getOptionsFromString(options);
-  applyToAnalysisOptions(context.analysisOptions, optionMap);
-}
-
-ErrorProcessor? getProcessor(AnalysisError error) =>
-    ErrorProcessor.getProcessor(context.analysisOptions, error);
-
-class TestContext extends AnalysisContextImpl {
-  TestContext()
-      : super(
-          SynchronousSession(
-            AnalysisOptionsImpl(),
-            DeclaredVariables(),
-          ),
-          _SourceFactoryMock(),
-        );
-}
-
-class _SourceFactoryMock implements SourceFactory {
-  @override
-  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+  ErrorProcessor? getProcessor(AnalysisError error) {
+    return ErrorProcessor.getProcessor(analysisOptions, error);
+  }
 }
diff --git a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
index 690c472..403a89e 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -1136,10 +1136,11 @@
     });
   }
 
+  /// TODO(scheglov) Test discovery of a sibling library
   test_newFile_partOfName() async {
-    final a = newFile('$testPackageLibPath/a.dart', r'''
+    final a = newFile('$testPackageLibPath/nested/a.dart', r'''
 library my.lib;
-part 'b.dart';
+part '../b.dart';
 ''');
 
     final b = newFile('$testPackageLibPath/b.dart', r'''
@@ -2239,7 +2240,11 @@
       Uint32List(0),
       Uint32List(0),
       featureSetProvider,
-      fileContentCache: FileContentCache.ephemeral(resourceProvider),
+      fileContentStrategy: StoredFileContentStrategy(
+        FileContentCache.ephemeral(resourceProvider),
+      ),
+      prefetchFiles: null,
+      isGenerated: (_) => false,
     );
   }
 
diff --git a/pkg/analyzer/test/src/dart/micro/file_resolution.dart b/pkg/analyzer/test/src/dart/micro/file_resolution.dart
index 4aedd00..db6d5af 100644
--- a/pkg/analyzer/test/src/dart/micro/file_resolution.dart
+++ b/pkg/analyzer/test/src/dart/micro/file_resolution.dart
@@ -7,8 +7,8 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/analysis/byte_store.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart';
-import 'package:analyzer/src/dart/micro/library_graph.dart';
 import 'package:analyzer/src/dart/micro/resolve_file.dart';
 import 'package:analyzer/src/dart/sdk/sdk.dart';
 import 'package:analyzer/src/test_utilities/find_element.dart';
@@ -96,7 +96,7 @@
       getFileDigest: (String path) => _getDigest(path),
       workspace: workspace,
       prefetchFiles: null,
-      isGenerated: null,
+      isGenerated: (_) => false,
     );
     fileResolver.testView = testData;
   }
diff --git a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
index 1ad2d13..91d48d2a9 100644
--- a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
+++ b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
@@ -5,6 +5,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/dart/error/syntactic_errors.dart';
 import 'package:analyzer/src/dart/micro/resolve_file.dart';
@@ -362,9 +363,12 @@
 }
 ''');
 
-    expect(() async {
+    try {
       await resolveFile(a.path);
-    }, throwsStateError);
+      fail('Expected StateError');
+    } on StateError {
+      // OK
+    }
 
     // Notify the resolver about b.dart, it is OK now.
     fileResolver.changeFiles([b.path]);
@@ -2465,8 +2469,7 @@
   }
 
   test_resolve_libraryWithPart_noLibraryDiscovery() async {
-    var partPath = '/workspace/dart/test/lib/a.dart';
-    newFile(partPath, r'''
+    newFile('/workspace/dart/test/lib/a.dart', r'''
 part of 'test.dart';
 
 class A {}
@@ -2480,11 +2483,12 @@
 
     // We started resolution from the library, and then followed to the part.
     // So, the part knows its library, there is no need to discover it.
-    _assertDiscoveredLibraryForParts([]);
+    // TODO(scheglov) Use textual dump
+    // _assertDiscoveredLibraryForParts([]);
   }
 
   test_resolve_part_of_name() async {
-    newFile('/workspace/dart/test/lib/a.dart', r'''
+    final a = newFile('/workspace/dart/test/lib/a.dart', r'''
 library my.lib;
 
 part 'test.dart';
@@ -2503,11 +2507,15 @@
 }
 ''');
 
-    _assertDiscoveredLibraryForParts([result.path]);
+    // TODO(scheglov) Use textual dump
+    final fsState = fileResolver.fsState!;
+    final testState = fsState.getExistingFileForResource(testFile)!;
+    final testKind = testState.kind as PartFileStateKind;
+    expect(testKind.library?.file, fsState.getExistingFileForResource(a));
   }
 
   test_resolve_part_of_uri() async {
-    newFile('/workspace/dart/test/lib/a.dart', r'''
+    final a = newFile('/workspace/dart/test/lib/a.dart', r'''
 part 'test.dart';
 
 class A {
@@ -2524,7 +2532,11 @@
 }
 ''');
 
-    _assertDiscoveredLibraryForParts([result.path]);
+    // TODO(scheglov) Use textual dump
+    final fsState = fileResolver.fsState!;
+    final testState = fsState.getExistingFileForResource(testFile)!;
+    final testKind = testState.kind as PartFileStateKind;
+    expect(testKind.library?.file, fsState.getExistingFileForResource(a));
   }
 
   test_resolveFile_cache() async {
@@ -2699,10 +2711,6 @@
     ]);
   }
 
-  void _assertDiscoveredLibraryForParts(List<String> expected) {
-    expect(fileResolver.fsState!.testView.partsDiscoveredLibraries, expected);
-  }
-
   void _assertResolvedFiles(
     List<File> expected, {
     bool andClear = true,
diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
index 38b8304..1a89f2a 100644
--- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
@@ -47,14 +47,16 @@
   return node;
 }
 
-/// Gets the outer-most node with the same offset/length as node.
+/// Gets the outer-most node with the same offset as node.
+///
+/// This reduces the number of nodes the visitor needs to walk when collecting
+/// navigation for a specific location in the file.
 AstNode _getOutermostNode(AstNode node) {
   AstNode? current = node;
   while (current != null &&
       current.parent != null &&
       current != current.parent &&
-      current.offset == current.parent!.offset &&
-      current.length == current.parent!.length) {
+      current.offset == current.parent!.offset) {
     current = current.parent;
   }
   return current ?? node;
@@ -91,12 +93,6 @@
     collector.addRegion(offset, length, kind, location, targetElement: element);
   }
 
-  void _addRegion_nodeStart_nodeEnd(AstNode a, AstNode b, Element? element) {
-    var offset = a.offset;
-    var length = b.end - offset;
-    _addRegion(offset, length, element);
-  }
-
   void _addRegionForNode(AstNode? node, Element? element) {
     if (node == null) {
       return;
@@ -265,15 +261,19 @@
 
   @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
-    // associate constructor with "T" or "T.name"
-    {
-      AstNode firstNode = node.returnType;
-      AstNode? lastNode = node.name;
-      lastNode ??= firstNode;
-      computer._addRegion_nodeStart_nodeEnd(
-          firstNode, lastNode, node.declaredElement);
+    // For a default constructor, override the class name to be the declaration
+    // itself rather than linking to the class.
+    var name = node.name;
+    if (name == null) {
+      computer._addRegionForNode(node.returnType, node.declaredElement);
+    } else {
+      node.returnType.accept(this);
+      name.accept(this);
     }
-    super.visitConstructorDeclaration(node);
+    node.parameters.accept(this);
+    node.initializers.accept(this);
+    node.redirectedConstructor?.accept(this);
+    node.body.accept(this);
   }
 
   @override
@@ -292,7 +292,10 @@
         name.prefix.accept(this);
         className = name.identifier;
       }
-      computer._addRegionForNode(className, element);
+      // For a named constructor, the class name points at the class.
+      var classNameTargetElement =
+          node.name != null ? className.staticElement : element;
+      computer._addRegionForNode(className, classNameTargetElement);
     }
     // <TypeA, TypeB>
     typeName.typeArguments?.accept(this);
@@ -418,9 +421,6 @@
 
   @override
   void visitSimpleIdentifier(SimpleIdentifier node) {
-    if (node.parent is ConstructorDeclaration) {
-      return;
-    }
     var element = node.writeOrReadElement;
     computer._addRegionForNode(node, element);
   }
diff --git a/pkg/compiler/lib/src/js_backend/native_data.dart b/pkg/compiler/lib/src/js_backend/native_data.dart
index 67f6246..4226f5a 100644
--- a/pkg/compiler/lib/src/js_backend/native_data.dart
+++ b/pkg/compiler/lib/src/js_backend/native_data.dart
@@ -15,7 +15,7 @@
 import '../js_model/js_world_builder.dart' show identity, JsToFrontendMap;
 import '../kernel/element_map.dart';
 import '../native/behavior.dart' show NativeBehavior;
-import '../serialization/serialization.dart';
+import '../serialization/serialization_interfaces.dart';
 import '../util/util.dart';
 
 import 'native_data_interfaces.dart' as interfaces;
diff --git a/pkg/compiler/lib/src/native/behavior.dart b/pkg/compiler/lib/src/native/behavior.dart
index 20d3d53..545440e 100644
--- a/pkg/compiler/lib/src/native/behavior.dart
+++ b/pkg/compiler/lib/src/native/behavior.dart
@@ -17,6 +17,9 @@
 import '../universe/side_effects.dart' show SideEffects;
 import 'js.dart';
 
+import 'native_throw_behavior.dart';
+export 'native_throw_behavior.dart';
+
 typedef TypeLookup = Object /*DartType|SpecialType*/
     Function(String typeString, {bool required});
 
@@ -43,94 +46,6 @@
   }
 }
 
-/// Description of the exception behaviour of native code.
-class NativeThrowBehavior {
-  static const NativeThrowBehavior NEVER = NativeThrowBehavior._(0);
-  static const NativeThrowBehavior MAY = NativeThrowBehavior._(1);
-
-  /// Throws only if first argument is null.
-  static const NativeThrowBehavior NULL_NSM = NativeThrowBehavior._(2);
-
-  /// Throws if first argument is null, then may throw.
-  static const NativeThrowBehavior NULL_NSM_THEN_MAY = NativeThrowBehavior._(3);
-
-  final int _bits;
-  const NativeThrowBehavior._(this._bits);
-
-  bool get canThrow => this != NEVER;
-
-  /// Does this behavior always throw a noSuchMethod check on a null first
-  /// argument before any side effect or other exception?
-  bool get isNullNSMGuard => this == NULL_NSM || this == NULL_NSM_THEN_MAY;
-
-  /// Does this behavior always act as a null noSuchMethod check, and has no
-  /// other throwing behavior?
-  bool get isOnlyNullNSMGuard => this == NULL_NSM;
-
-  /// Returns the behavior if we assume the first argument is not null.
-  NativeThrowBehavior get onNonNull {
-    if (this == NULL_NSM) return NEVER;
-    if (this == NULL_NSM_THEN_MAY) return MAY;
-    return this;
-  }
-
-  @override
-  String toString() {
-    if (this == NEVER) return 'never';
-    if (this == MAY) return 'may';
-    if (this == NULL_NSM) return 'null(1)';
-    if (this == NULL_NSM_THEN_MAY) return 'null(1)+may';
-    return 'NativeThrowBehavior($_bits)';
-  }
-
-  /// Canonical list of marker values.
-  ///
-  /// Added to make [NativeThrowBehavior] enum-like.
-  static const List<NativeThrowBehavior> values = [
-    NEVER,
-    MAY,
-    NULL_NSM,
-    NULL_NSM_THEN_MAY,
-  ];
-
-  /// Index to this marker within [values].
-  ///
-  /// Added to make [NativeThrowBehavior] enum-like.
-  int get index => values.indexOf(this);
-
-  /// Deserializer helper.
-  static NativeThrowBehavior _bitsToValue(int bits) {
-    switch (bits) {
-      case 0:
-        return NEVER;
-      case 1:
-        return MAY;
-      case 2:
-        return NULL_NSM;
-      case 3:
-        return NULL_NSM_THEN_MAY;
-      default:
-        return null;
-    }
-  }
-
-  /// Sequence operator.
-  NativeThrowBehavior then(NativeThrowBehavior second) {
-    if (this == NEVER) return second;
-    if (this == MAY) return MAY;
-    if (this == NULL_NSM_THEN_MAY) return NULL_NSM_THEN_MAY;
-    assert(this == NULL_NSM);
-    if (second == NEVER) return this;
-    return NULL_NSM_THEN_MAY;
-  }
-
-  /// Choice operator.
-  NativeThrowBehavior or(NativeThrowBehavior other) {
-    if (this == other) return this;
-    return MAY;
-  }
-}
-
 /// A summary of the behavior of a native element.
 ///
 /// Native code can return values of one type and cause native subtypes of
@@ -227,8 +142,8 @@
       behavior.codeTemplateText = codeTemplateText;
       behavior.codeTemplate = js.js.parseForeignJS(codeTemplateText);
     }
-    behavior.throwBehavior = NativeThrowBehavior._bitsToValue(throwBehavior);
-    assert(behavior.throwBehavior._bits == throwBehavior);
+    behavior.throwBehavior = NativeThrowBehavior.bitsToValue(throwBehavior);
+    assert(behavior.throwBehavior.valueToBits() == throwBehavior);
     behavior.isAllocation = isAllocation;
     behavior.useGvn = useGvn;
     return behavior;
@@ -259,7 +174,7 @@
     writeTypes(typesInstantiated);
     sink.writeStringOrNull(codeTemplateText);
     sideEffects.writeToDataSink(sink);
-    sink.writeInt(throwBehavior._bits);
+    sink.writeInt(throwBehavior.valueToBits());
     sink.writeBool(isAllocation);
     sink.writeBool(useGvn);
     sink.end(tag);
diff --git a/pkg/compiler/lib/src/native/js.dart b/pkg/compiler/lib/src/native/js.dart
index 9c69ae7..9f44343 100644
--- a/pkg/compiler/lib/src/native/js.dart
+++ b/pkg/compiler/lib/src/native/js.dart
@@ -2,11 +2,9 @@
 // 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 '../js/js.dart' as js;
 import '../universe/side_effects.dart' show SideEffects;
-import 'behavior.dart';
+import 'native_throw_behavior.dart' show NativeThrowBehavior;
 
 class HasCapturedPlaceholders extends js.BaseVisitorVoid {
   HasCapturedPlaceholders._();
diff --git a/pkg/compiler/lib/src/native/native_throw_behavior.dart b/pkg/compiler/lib/src/native/native_throw_behavior.dart
new file mode 100644
index 0000000..cf50ba8
--- /dev/null
+++ b/pkg/compiler/lib/src/native/native_throw_behavior.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Description of the exception behaviour of native code.
+class NativeThrowBehavior {
+  static const NativeThrowBehavior NEVER = NativeThrowBehavior._(0);
+  static const NativeThrowBehavior MAY = NativeThrowBehavior._(1);
+
+  /// Throws only if first argument is null.
+  static const NativeThrowBehavior NULL_NSM = NativeThrowBehavior._(2);
+
+  /// Throws if first argument is null, then may throw.
+  static const NativeThrowBehavior NULL_NSM_THEN_MAY = NativeThrowBehavior._(3);
+
+  final int _bits;
+  const NativeThrowBehavior._(this._bits);
+
+  bool get canThrow => this != NEVER;
+
+  /// Does this behavior always throw a noSuchMethod check on a null first
+  /// argument before any side effect or other exception?
+  bool get isNullNSMGuard => this == NULL_NSM || this == NULL_NSM_THEN_MAY;
+
+  /// Does this behavior always act as a null noSuchMethod check, and has no
+  /// other throwing behavior?
+  bool get isOnlyNullNSMGuard => this == NULL_NSM;
+
+  /// Returns the behavior if we assume the first argument is not null.
+  NativeThrowBehavior get onNonNull {
+    if (this == NULL_NSM) return NEVER;
+    if (this == NULL_NSM_THEN_MAY) return MAY;
+    return this;
+  }
+
+  @override
+  String toString() {
+    if (this == NEVER) return 'never';
+    if (this == MAY) return 'may';
+    if (this == NULL_NSM) return 'null(1)';
+    if (this == NULL_NSM_THEN_MAY) return 'null(1)+may';
+    return 'NativeThrowBehavior($_bits)';
+  }
+
+  /// Canonical list of marker values.
+  ///
+  /// Added to make [NativeThrowBehavior] enum-like.
+  static const List<NativeThrowBehavior> values = [
+    NEVER,
+    MAY,
+    NULL_NSM,
+    NULL_NSM_THEN_MAY,
+  ];
+
+  /// Index to this marker within [values].
+  ///
+  /// Added to make [NativeThrowBehavior] enum-like.
+  int get index => values.indexOf(this);
+
+  /// Deserializer helper.
+  static NativeThrowBehavior bitsToValue(int bits) {
+    switch (bits) {
+      case 0:
+        return NEVER;
+      case 1:
+        return MAY;
+      case 2:
+        return NULL_NSM;
+      case 3:
+        return NULL_NSM_THEN_MAY;
+      default:
+        throw StateError('Unknown serialized NativeThrowBehavior: $bits');
+    }
+  }
+
+  int valueToBits() => _bits;
+
+  /// Sequence operator.
+  NativeThrowBehavior then(NativeThrowBehavior second) {
+    if (this == NEVER) return second;
+    if (this == MAY) return MAY;
+    if (this == NULL_NSM_THEN_MAY) return NULL_NSM_THEN_MAY;
+    assert(this == NULL_NSM);
+    if (second == NEVER) return this;
+    return NULL_NSM_THEN_MAY;
+  }
+
+  /// Choice operator.
+  NativeThrowBehavior or(NativeThrowBehavior other) {
+    if (this == other) return this;
+    return MAY;
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/serialization_interfaces.dart b/pkg/compiler/lib/src/serialization/serialization_interfaces.dart
index 5c2e61b..8a4ec2b 100644
--- a/pkg/compiler/lib/src/serialization/serialization_interfaces.dart
+++ b/pkg/compiler/lib/src/serialization/serialization_interfaces.dart
@@ -79,6 +79,8 @@
 
   void writeLibrary(covariant LibraryEntity value); // IndexedLibrary
   void writeLibraryOrNull(covariant LibraryEntity? value); // IndexedLibrary
+  void writeLibraryMap<V>(Map<LibraryEntity, V>? map, void f(V value),
+      {bool allowNull = false});
 
   void writeDartTypeNode(ir.DartType value);
   void writeDartTypeNodeOrNull(ir.DartType? value);
@@ -151,6 +153,8 @@
 
   LibraryEntity readLibrary(); // IndexedLibrary;
   LibraryEntity? readLibraryOrNull(); // IndexedLibrary;
+  Map<K, V> readLibraryMap<K extends LibraryEntity, V>(V f());
+  Map<K, V>? readLibraryMapOrNull<K extends LibraryEntity, V>(V f());
 
   ir.DartType readDartTypeNode();
   ir.DartType? readDartTypeNodeOrNull();
diff --git a/pkg/compiler/lib/src/serialization/sink.dart b/pkg/compiler/lib/src/serialization/sink.dart
index 0d3bef6..c3d96ab 100644
--- a/pkg/compiler/lib/src/serialization/sink.dart
+++ b/pkg/compiler/lib/src/serialization/sink.dart
@@ -729,6 +729,7 @@
   ///
   /// This is a convenience method to be used together with
   /// [DataSourceReader.readLibraryMap].
+  @override
   void writeLibraryMap<V>(Map<LibraryEntity, V> map, void f(V value),
       {bool allowNull = false}) {
     if (map == null) {
diff --git a/pkg/compiler/lib/src/serialization/source.dart b/pkg/compiler/lib/src/serialization/source.dart
index 6136b59..287a402 100644
--- a/pkg/compiler/lib/src/serialization/source.dart
+++ b/pkg/compiler/lib/src/serialization/source.dart
@@ -945,17 +945,26 @@
     return null;
   }
 
-  /// Reads a library from library entities to [V] values
-  /// from this data source, calling [f] to read each value from the data
-  /// source. If [emptyAsNull] is `true`, `null` is returned instead of an empty
-  /// map.
+  /// Reads a library from library entities to [V] values from this data source,
+  /// calling [f] to read each value from the data source.
   ///
   /// This is a convenience method to be used together with
   /// [DataSinkWriter.writeLibraryMap].
-  Map<K, V> readLibraryMap<K extends LibraryEntity, V>(V f(),
-      {bool emptyAsNull = false}) {
+  @override
+  Map<K, V> readLibraryMap<K extends LibraryEntity, V>(V f()) {
+    return readLibraryMapOrNull<K, V>(f) ?? {};
+  }
+
+  /// Reads a library from library entities to [V] values from this data source,
+  /// calling [f] to read each value from the data source. `null` is returned
+  /// instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSinkWriter.writeLibraryMap].
+  @override
+  Map<K, V> /*?*/ readLibraryMapOrNull<K extends LibraryEntity, V>(V f()) {
     int count = readInt();
-    if (count == 0 && emptyAsNull) return null;
+    if (count == 0) return null;
     Map<K, V> map = {};
     for (int i = 0; i < count; i++) {
       LibraryEntity library = readLibrary();
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 573444a..b9c10ba 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -159,7 +159,7 @@
   }
 
   visitPostDominatorTree(HGraph graph) {
-    // Recusion free version of:
+    // Recursion-free version of:
     //
     //     void visitBasicBlockAndSuccessors(HBasicBlock block) {
     //       List dominated = block.dominatedBlocks;
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 8fcda3b..e608892 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -2422,7 +2422,6 @@
   final JClosedWorld closedWorld;
   final SsaOptimizerTask optimizer;
   HGraph _graph;
-  SsaLiveBlockAnalyzer analyzer;
   Map<HInstruction, bool> trivialDeadStoreReceivers = Maplet();
   bool eliminatedSideEffects = false;
   bool newGvnCandidates = false;
@@ -2432,14 +2431,10 @@
   AbstractValueDomain get _abstractValueDomain =>
       closedWorld.abstractValueDomain;
 
-  HInstruction zapInstructionCache;
-  HInstruction get zapInstruction {
-    if (zapInstructionCache == null) {
-      // A constant with no type does not pollute types at phi nodes.
-      zapInstructionCache = analyzer.graph.addConstantUnreachable(closedWorld);
-    }
-    return zapInstructionCache;
-  }
+  // A constant with no type does not pollute types at phi nodes.
+  HInstruction _zapInstruction;
+  HInstruction get zapInstruction =>
+      _zapInstruction ??= _graph.addConstantUnreachable(closedWorld);
 
   /// Determines whether we can delete [instruction] because the only thing it
   /// does is throw the same exception as the next instruction that throws or
@@ -2479,7 +2474,7 @@
             successor = condition.constant is TrueConstantValue
                 ? current.thenBlock
                 : current.elseBlock;
-            assert(!analyzer.isDeadBlock(successor));
+            assert(successor.isLive);
           }
         }
         if (successor != null && successor.id > current.block.id) {
@@ -2538,10 +2533,17 @@
   @override
   void visitGraph(HGraph graph) {
     _graph = graph;
-    analyzer = SsaLiveBlockAnalyzer(graph, closedWorld, optimizer);
-    analyzer.analyze();
+    _zapInstruction = null;
+    _computeLiveness();
     visitPostDominatorTree(graph);
-    cleanPhis();
+  }
+
+  void _computeLiveness() {
+    var analyzer = SsaLiveBlockAnalyzer(_graph, closedWorld, optimizer);
+    analyzer.analyze();
+    for (HBasicBlock block in _graph.blocks) {
+      block.isLive = analyzer.isLiveBlock(block);
+    }
   }
 
   @override
@@ -2551,14 +2553,12 @@
 
   @override
   void visitBasicBlock(HBasicBlock block) {
-    bool isDeadBlock = analyzer.isDeadBlock(block);
-    block.isLive = !isDeadBlock;
     simplifyControlFlow(block);
     // Start from the last non-control flow instruction in the block.
     HInstruction instruction = block.last.previous;
     while (instruction != null) {
       var previous = instruction.previous;
-      if (isDeadBlock) {
+      if (!block.isLive) {
         eliminatedSideEffects =
             eliminatedSideEffects || instruction.sideEffects.hasSideEffects();
         removeUsers(instruction);
@@ -2574,10 +2574,48 @@
 
   void simplifyPhi(HPhi phi) {
     // Remove an unused HPhi so that the inputs can become potentially dead.
+    if (!phi.block.isLive) {
+      removeUsers(phi);
+    }
+
     if (phi.usedBy.isEmpty) {
       phi.block.removePhi(phi);
       return;
     }
+
+    // Run through the phis of the block and replace them with their input that
+    // comes from the only live predecessor if that dominates the phi.
+    //
+    // TODO(sra): If the input is directly in the only live predecessor, it
+    // might be possible to move it into [block] (e.g. all its inputs are
+    // dominating.)
+    // Find the index of the single live predecessor if it exists.
+    List<HBasicBlock> predecessors = phi.block.predecessors;
+    int indexOfLive = -1;
+    for (int i = 0; i < predecessors.length; i++) {
+      if (predecessors[i].isLive) {
+        if (indexOfLive >= 0) {
+          indexOfLive = -1;
+          break;
+        }
+        indexOfLive = i;
+      }
+    }
+
+    if (indexOfLive >= 0) {
+      HInstruction replacement = phi.inputs[indexOfLive];
+      if (replacement.dominates(phi)) {
+        phi.block.rewrite(phi, replacement);
+        phi.block.removePhi(phi);
+        if (replacement.sourceElement == null &&
+            phi.sourceElement != null &&
+            replacement is! HThis) {
+          replacement.sourceElement = phi.sourceElement;
+        }
+        return;
+      }
+    }
+
     // If the phi is of the form `phi(x, HTypeKnown(x))`, it does not strengthen
     // `x`.  We can replace the phi with `x` to potentially make the HTypeKnown
     // refinement node dead and potentially make a HIf control no HPhis.
@@ -2681,49 +2719,6 @@
     }
   }
 
-  void cleanPhis() {
-    L:
-    for (HBasicBlock block in _graph.blocks) {
-      List<HBasicBlock> predecessors = block.predecessors;
-      // Zap all inputs to phis that correspond to dead blocks.
-      block.forEachPhi((HPhi phi) {
-        for (int i = 0; i < phi.inputs.length; ++i) {
-          if (!predecessors[i].isLive && phi.inputs[i] != zapInstruction) {
-            phi.replaceInput(i, zapInstruction);
-          }
-        }
-      });
-      if (predecessors.length < 2) continue L;
-      // Find the index of the single live predecessor if it exists.
-      int indexOfLive = -1;
-      for (int i = 0; i < predecessors.length; i++) {
-        if (predecessors[i].isLive) {
-          if (indexOfLive >= 0) continue L;
-          indexOfLive = i;
-        }
-      }
-      // Run through the phis of the block and replace them with their input
-      // that comes from the only live predecessor if that dominates the phi.
-      //
-      // TODO(sra): If the input is directly in the only live predecessor, it
-      // might be possible to move it into [block] (e.g. all its inputs are
-      // dominating.)
-      block.forEachPhi((HPhi phi) {
-        HInstruction replacement =
-            (indexOfLive >= 0) ? phi.inputs[indexOfLive] : zapInstruction;
-        if (replacement.dominates(phi)) {
-          block.rewrite(phi, replacement);
-          block.removePhi(phi);
-          if (replacement.sourceElement == null &&
-              phi.sourceElement != null &&
-              replacement is! HThis) {
-            replacement.sourceElement = phi.sourceElement;
-          }
-        }
-      });
-    }
-  }
-
   void removeUsers(HInstruction instruction) {
     instruction.usedBy.forEach((user) {
       removeInput(user, instruction);
@@ -2756,7 +2751,7 @@
 
   Map<HInstruction, Range> get ranges => optimizer.ranges;
 
-  bool isDeadBlock(HBasicBlock block) => !live.contains(block);
+  bool isLiveBlock(HBasicBlock block) => live.contains(block);
 
   void analyze() {
     markBlockLive(graph.entry);
diff --git a/pkg/dart2js_info/lib/proto_info_codec.dart b/pkg/dart2js_info/lib/proto_info_codec.dart
index 7007203..e81cb1a 100644
--- a/pkg/dart2js_info/lib/proto_info_codec.dart
+++ b/pkg/dart2js_info/lib/proto_info_codec.dart
@@ -2,8 +2,6 @@
 // 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.11
-
 /// Converters and codecs for converting between Protobuf and [Info] classes.
 import 'dart:convert';
 
@@ -31,7 +29,6 @@
   final Set<int> usedIds = <int>{};
 
   Id idFor(Info info) {
-    if (info == null) return null;
     var serializedId = ids[info];
     if (serializedId != null) return serializedId;
 
@@ -45,7 +42,6 @@
       // No name and no parent, so `longName` isn't helpful
       assert(info.name.isEmpty);
       assert(info.parent == null);
-      assert(info.code != null);
       // Instead, use the content of the code.
       id = info.code.first.text.hashCode;
     } else {
@@ -62,10 +58,11 @@
   AllInfoPB convert(AllInfo input) => _convertToAllInfoPB(input);
 
   DependencyInfoPB _convertToDependencyInfoPB(DependencyInfo info) {
-    var result = DependencyInfoPB()
-      ..targetId = idFor(info.target)?.serializedId;
+    var result = DependencyInfoPB();
+    final targetId = idFor(info.target).serializedId;
+    result.targetId = targetId;
     if (info.mask != null) {
-      result.mask = info.mask;
+      result.mask = info.mask!;
     }
     return result;
   }
@@ -123,21 +120,13 @@
       ..functionModifiers = _convertToFunctionModifiers(info.modifiers)
       ..inlinedCount = info.inlinedCount ?? 0;
 
-    if (info.returnType != null) {
-      proto.returnType = info.returnType;
-    }
+    proto.returnType = info.returnType;
 
-    if (info.inferredReturnType != null) {
-      proto.inferredReturnType = info.inferredReturnType;
-    }
+    proto.inferredReturnType = info.inferredReturnType;
 
-    if (info.code != null) {
-      proto.code = info.code.map((c) => c.text).join('\n');
-    }
+    proto.code = info.code.map((c) => c.text).join('\n');
 
-    if (info.sideEffects != null) {
-      proto.sideEffects = info.sideEffects;
-    }
+    proto.sideEffects = info.sideEffects;
 
     proto.childrenIds
         .addAll(info.closures.map(((closure) => idFor(closure).serializedId)));
@@ -152,12 +141,10 @@
       ..inferredType = info.inferredType
       ..isConst = info.isConst;
 
-    if (info.code != null) {
-      proto.code = info.code.map((c) => c.text).join('\n');
-    }
+    proto.code = info.code.map((c) => c.text).join('\n');
 
     if (info.initializer != null) {
-      proto.initializerId = idFor(info.initializer).serializedId;
+      proto.initializerId = idFor(info.initializer!).serializedId;
     }
 
     proto.childrenIds
@@ -172,7 +159,7 @@
 
   static OutputUnitInfoPB _convertToOutputUnitInfoPB(OutputUnitInfo info) {
     final proto = OutputUnitInfoPB();
-    proto.imports.addAll(info.imports.where((import) => import != null));
+    proto.imports.addAll(info.imports);
     return proto;
   }
 
@@ -190,23 +177,21 @@
       ..serializedId = idFor(info).serializedId
       ..size = info.size;
 
-    if (info.name != null) {
-      proto.name = info.name;
-    }
+    proto.name = info.name;
 
     if (info.parent != null) {
-      proto.parentId = idFor(info.parent).serializedId;
+      proto.parentId = idFor(info.parent!).serializedId;
     }
 
     if (info.coverageId != null) {
-      proto.coverageId = info.coverageId;
+      proto.coverageId = info.coverageId!;
     }
 
     if (info is BasicInfo && info.outputUnit != null) {
       // TODO(lorenvs): Similar to the JSON codec, omit this for the default
       // output unit. At the moment, there is no easy way to identify which
       // output unit is the default on [OutputUnitInfo].
-      proto.outputUnitId = idFor(info.outputUnit).serializedId;
+      proto.outputUnitId = idFor(info.outputUnit!).serializedId;
     }
 
     if (info is CodeInfo) {
@@ -244,15 +229,15 @@
       ..compilationDuration = Int64(info.compilationDuration.inMicroseconds)
       ..toProtoDuration = Int64(info.toJsonDuration.inMicroseconds)
       ..dumpInfoDuration = Int64(info.dumpInfoDuration.inMicroseconds)
-      ..noSuchMethodEnabled = info.noSuchMethodEnabled ?? false
-      ..isRuntimeTypeUsed = info.isRuntimeTypeUsed ?? false
-      ..isIsolateUsed = info.isIsolateInUse ?? false
-      ..isFunctionApplyUsed = info.isFunctionApplyUsed ?? false
-      ..isMirrorsUsed = info.isMirrorsUsed ?? false
-      ..minified = info.minified ?? false;
+      ..noSuchMethodEnabled = info.noSuchMethodEnabled
+      ..isRuntimeTypeUsed = info.isRuntimeTypeUsed
+      ..isIsolateUsed = info.isIsolateInUse
+      ..isFunctionApplyUsed = info.isFunctionApplyUsed
+      ..isMirrorsUsed = info.isMirrorsUsed
+      ..minified = info.minified;
 
     if (info.dart2jsVersion != null) {
-      result.dart2jsVersion = info.dart2jsVersion;
+      result.dart2jsVersion = info.dart2jsVersion!;
     }
     return result;
   }
@@ -283,7 +268,7 @@
   }
 
   AllInfoPB _convertToAllInfoPB(AllInfo info) {
-    final proto = AllInfoPB()..program = _convertToProgramInfoPB(info.program);
+    final proto = AllInfoPB()..program = _convertToProgramInfoPB(info.program!);
 
     proto.allInfos.addEntries(_convertToAllInfosEntries(info.libraries));
     proto.allInfos.addEntries(_convertToAllInfosEntries(info.classes));
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index bf65127..de13bd0 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -422,8 +422,8 @@
         StringLiteral(expected),
         translator
             .translateType(translator.coreTypes.stringNonNullableRawType));
-    _call(translator.stackTraceCurrent.reference);
-    _call(translator.throwWasmRefError.reference);
+    call(translator.stackTraceCurrent.reference);
+    call(translator.throwWasmRefError.reference);
     b.unreachable();
   }
 
@@ -436,7 +436,7 @@
     return expectedType;
   }
 
-  w.ValueType _call(Reference target) {
+  w.ValueType call(Reference target) {
     w.BaseFunction targetFunction = translator.functions.getFunction(target);
     if (translator.shouldInline(target)) {
       List<w.Local> inlinedLocals =
@@ -492,7 +492,7 @@
           this, TypeParameterType(typeParam, Nullability.nonNullable));
     }
     _visitArguments(node.arguments, node.targetReference, 1);
-    _call(node.targetReference);
+    call(node.targetReference);
   }
 
   @override
@@ -511,7 +511,7 @@
     }
     _visitArguments(node.arguments, node.targetReference,
         1 + supertype.typeArguments.length);
-    _call(node.targetReference);
+    call(node.targetReference);
   }
 
   @override
@@ -910,7 +910,7 @@
       nonNullableType =
           translator.classInfo[translator.stringBaseClass]!.nonNullableType;
       nullableType = nonNullableType.withNullability(true);
-      compare = () => _call(translator.stringEquals.reference);
+      compare = () => call(translator.stringEquals.reference);
     } else {
       // Object switch
       assert(check<InvalidExpression, InstanceConstant>());
@@ -1108,7 +1108,7 @@
       b.ref_as_non_null();
     }
     _visitArguments(node.arguments, node.targetReference, 1);
-    _call(node.targetReference);
+    call(node.targetReference);
     if (expectedType != voidMarker) {
       b.local_get(temp);
       return temp.type;
@@ -1124,7 +1124,7 @@
     if (intrinsicResult != null) return intrinsicResult;
 
     _visitArguments(node.arguments, node.targetReference, 0);
-    return _call(node.targetReference);
+    return call(node.targetReference);
   }
 
   Member _lookupSuperTarget(Member interfaceTarget, {required bool setter}) {
@@ -1143,7 +1143,7 @@
     w.ValueType thisType = visitThis(receiverType);
     translator.convertType(function, thisType, receiverType);
     _visitArguments(node.arguments, target, 1);
-    return _call(target);
+    return call(target);
   }
 
   @override
@@ -1182,7 +1182,7 @@
           translator.functions.getFunction(singleTarget.reference);
       wrap(node.receiver, targetFunction.type.inputs.first);
       _visitArguments(node.arguments, node.interfaceTargetReference, 1);
-      return _call(singleTarget.reference);
+      return call(singleTarget.reference);
     }
     return _virtualCall(node, target,
         (signature) => wrap(node.receiver, signature.inputs.first), (_) {
@@ -1258,7 +1258,7 @@
       if (singleTarget != null) {
         left();
         right();
-        _call(singleTarget.reference);
+        call(singleTarget.reference);
       } else {
         _virtualCall(node, node.interfaceTarget, left, right,
             getter: false, setter: false);
@@ -1307,7 +1307,7 @@
       assert(selector.targetCount <= 1);
       if (selector.targetCount == 1) {
         pushArguments(selector.signature);
-        return _call(selector.singularTarget!);
+        return call(selector.singularTarget!);
       } else {
         b.comment("Virtual call of ${selector.name} with no targets"
             " at ${node.location}");
@@ -1367,7 +1367,7 @@
           b.i32_const(id);
           b.i32_eq();
           b.if_(selector.signature.inputs, selector.signature.inputs);
-          _call(target);
+          call(target);
           b.br(block);
           b.end();
           implementations.remove(id);
@@ -1387,7 +1387,7 @@
       b.i32_const(pivotId);
       b.i32_lt_u();
       b.if_(selector.signature.inputs, selector.signature.inputs);
-      _call(target);
+      call(target);
       b.br(block);
       b.end();
       for (int id in sorted) {
@@ -1398,7 +1398,7 @@
     }
     // Call remaining implementation.
     Reference target = implementations.values.first;
-    _call(target);
+    call(target);
     b.end();
   }
 
@@ -1468,7 +1468,7 @@
     if (target is Field) {
       return translator.globals.readGlobal(b, target);
     } else {
-      return _call(target.reference);
+      return call(target.reference);
     }
   }
 
@@ -1502,7 +1502,7 @@
         temp = addLocal(translateType(dartTypeOf(node.value)));
         b.local_tee(temp);
       }
-      _call(target.reference);
+      call(target.reference);
       if (preserved) {
         b.local_get(temp!);
         return temp.type;
@@ -1622,7 +1622,7 @@
       w.BaseFunction targetFunction =
           translator.functions.getFunction(target.reference);
       wrap(receiver, targetFunction.type.inputs.single);
-      return _call(target.reference);
+      return call(target.reference);
     }
   }
 
@@ -1688,7 +1688,7 @@
         b.local_tee(temp);
         translator.convertType(function, temp.type, paramType);
       }
-      _call(target.reference);
+      call(target.reference);
     }
     if (preserved) {
       b.local_get(temp!);
@@ -1853,8 +1853,8 @@
     // We lower a null check to a br_on_non_null, throwing a [TypeError] in the
     // null case.
     b.br_on_non_null(nullCheckBlock);
-    _call(translator.stackTraceCurrent.reference);
-    _call(translator.throwNullCheckError.reference);
+    call(translator.stackTraceCurrent.reference);
+    call(translator.throwNullCheckError.reference);
     b.unreachable();
     b.end();
     return nonNullOperandType;
@@ -1905,13 +1905,13 @@
       StringConcatenation node, w.ValueType expectedType) {
     makeList(node.expressions, translator.fixedLengthListClass,
         InterfaceType(translator.stringBaseClass, Nullability.nonNullable));
-    return _call(translator.stringInterpolate.reference);
+    return call(translator.stringInterpolate.reference);
   }
 
   @override
   w.ValueType visitThrow(Throw node, w.ValueType expectedType) {
     wrap(node.expression, translator.topInfo.nonNullableType);
-    _call(translator.stackTraceCurrent.reference);
+    call(translator.stackTraceCurrent.reference);
 
     // At this point, we have the exception and the current stack trace on the
     // stack, so just throw them using the exception tag.
@@ -2101,8 +2101,8 @@
     b.br_if(asCheckBlock);
     b.local_get(operand);
     types.makeType(this, node.type);
-    _call(translator.stackTraceCurrent.reference);
-    _call(translator.throwAsCheckError.reference);
+    call(translator.stackTraceCurrent.reference);
+    call(translator.throwAsCheckError.reference);
     b.unreachable();
     b.end();
     b.local_get(operand);
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index fbf5eeb..e62bf7f 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -775,7 +775,6 @@
   @override
   ConstantInfo? visitTypeLiteralConstant(TypeLiteralConstant constant) {
     DartType type = constant.type;
-    assert(type is! TypeParameterType);
 
     ClassInfo info = translator.classInfo[types.classForType(type)]!;
     translator.functions.allocateClass(info.classId);
@@ -796,6 +795,18 @@
       } else {
         return _makeFunctionType(constant, type, info);
       }
+    } else if (type is TypeParameterType) {
+      // TODO(joshualitt): Handle generic function types.
+      assert(!types.isGenericFunctionTypeParameter(type));
+      int environmentIndex =
+          types.interfaceTypeEnvironment.lookup(type.parameter);
+      return createConstant(constant, info.nonNullableType, (function, b) {
+        b.i32_const(info.classId);
+        b.i32_const(initialIdentityHash);
+        types.encodeNullability(b, type);
+        b.i32_const(environmentIndex);
+        translator.struct_new(b, info);
+      });
     } else {
       assert(type is VoidType ||
           type is NeverType ||
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 5d166ca..e59f140 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -639,8 +639,10 @@
           codeGen.wrap(stackTrace, stackTraceType);
           b.throw_(translator.exceptionTag);
           return codeGen.voidMarker;
-        case "_getSubtypeMap":
-          return translator.types.makeSubtypeMap(b);
+        case "_getTypeRulesSupers":
+          return translator.types.makeTypeRulesSupers(b);
+        case "_getTypeRulesSubstitutions":
+          return translator.types.makeTypeRulesSubstitutions(b);
       }
     }
 
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 47a9670..ce2a835 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -97,6 +97,8 @@
   late final Class interfaceTypeClass;
   late final Class functionTypeClass;
   late final Class genericFunctionTypeClass;
+  late final Class interfaceTypeParameterTypeClass;
+  late final Class genericFunctionTypeParameterTypeClass;
   late final Class namedParameterClass;
   late final Class stackTraceClass;
   late final Class ffiCompoundClass;
@@ -119,7 +121,6 @@
   late final Procedure setFactory;
   late final Procedure setAdd;
   late final Procedure hashImmutableIndexNullable;
-  // TODO(joshualitt): Wire up runtime type checks.
   late final Procedure isSubtype;
   late final Map<Class, w.StorageType> builtinTypes;
   late final Map<w.ValueType, Class> boxedClasses;
@@ -213,6 +214,9 @@
     interfaceTypeClass = lookupCore("_InterfaceType");
     functionTypeClass = lookupCore("_FunctionType");
     genericFunctionTypeClass = lookupCore("_GenericFunctionType");
+    interfaceTypeParameterTypeClass = lookupCore("_InterfaceTypeParameterType");
+    genericFunctionTypeParameterTypeClass =
+        lookupCore("_GenericFunctionTypeParameterType");
     namedParameterClass = lookupCore("_NamedParameter");
     stackTraceClass = lookupCore("StackTrace");
     typeUniverseClass = lookupCore("_TypeUniverse");
diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart
index 53df180..371f1c9 100644
--- a/pkg/dart2wasm/lib/types.dart
+++ b/pkg/dart2wasm/lib/types.dart
@@ -7,9 +7,27 @@
 import 'package:dart2wasm/translator.dart';
 
 import 'package:kernel/ast.dart';
+import 'package:kernel/core_types.dart';
 
 import 'package:wasm_builder/wasm_builder.dart' as w;
 
+class InterfaceTypeEnvironment {
+  final Map<TypeParameter, int> typeOffsets = {};
+
+  void _add(InterfaceType type) {
+    Class cls = type.classNode;
+    if (typeOffsets.containsKey(cls)) {
+      return;
+    }
+    int i = 0;
+    for (TypeParameter typeParameter in cls.typeParameters) {
+      typeOffsets[typeParameter] = i++;
+    }
+  }
+
+  int lookup(TypeParameter typeParameter) => typeOffsets[typeParameter]!;
+}
+
 /// Helper class for building runtime types.
 class Types {
   final Translator translator;
@@ -19,6 +37,32 @@
   late final w.ValueType namedParametersExpectedType = classAndFieldToType(
       translator.functionTypeClass, FieldIndex.functionTypeNamedParameters);
 
+  /// A mapping from concrete subclass `classID` to [Map]s of superclass
+  /// `classID` and the necessary substitutions which must be performed to test
+  /// for a valid subtyping relationship.
+  late final Map<int, Map<int, List<DartType>>> typeRules = _buildTypeRules();
+
+  /// We will build the [interfaceTypeEnvironment] when building the
+  /// [typeRules].
+  final InterfaceTypeEnvironment interfaceTypeEnvironment =
+      InterfaceTypeEnvironment();
+
+  /// Because we can't currently support [Map]s in our `TypeUniverse`, we have
+  /// to decompose [typeRules] into two [Map]s based on [List]s.
+  ///
+  /// [typeRulesSupers] is a [List] where the index in the list is a subclasses'
+  /// `classID` and the value at that index is a [List] of superclass
+  /// `classID`s.
+  late final List<List<int>> typeRulesSupers = _buildTypeRulesSupers();
+
+  /// [typeRulesSubstitutions] is a [List] where the index in the list is a
+  /// subclasses' `classID` and the value at that index is a [List] indexed by
+  /// the index of the superclasses' `classID` in [typeRulesSuper] and the value
+  /// at that index is a [List] of [DartType]s which must be substituted for the
+  /// subtyping relationship to be valid.
+  late final List<List<List<DartType>>> typeRulesSubstitutions =
+      _buildTypeRulesSubstitutions();
+
   Types(this.translator);
 
   w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
@@ -34,46 +78,135 @@
   InterfaceType get namedParameterType =>
       InterfaceType(translator.namedParameterClass, Nullability.nonNullable);
 
-  /// Build a [Map<int, List<int>>] to store subtype information.
-  Map<int, List<int>> _buildSubtypeMap() {
-    List<ClassInfo> classes = translator.classes;
-    Map<int, List<int>> subtypeMap = {};
-    for (ClassInfo classInfo in classes) {
-      if (classInfo.cls == null) continue;
-      List<int> classIds = _getConcreteSubtypes(classInfo.cls!)
-          .map((cls) => translator.classInfo[cls]!.classId)
-          .where((classId) => classId != classInfo.classId)
-          .toList();
+  CoreTypes get coreTypes => translator.coreTypes;
 
-      if (classIds.isEmpty) continue;
-      subtypeMap[classInfo.classId] = classIds;
+  /// Builds a [Map<int, Map<int, List<DartType>>>] to store subtype
+  /// information.  The first key is the class id of a subtype. This returns a
+  /// map where each key is the class id of a transitively implemented super
+  /// type and each value is a list of the necessary type substitutions required
+  /// for the subtyping relationship to be valid.
+  Map<int, Map<int, List<DartType>>> _buildTypeRules() {
+    List<ClassInfo> classes = translator.classes;
+    Map<int, Map<int, List<DartType>>> subtypeMap = {};
+    for (ClassInfo classInfo in classes) {
+      ClassInfo superclassInfo = classInfo;
+
+      // We don't need type rules for any class without a superclass, or for
+      // classes whose supertype is [Object]. The latter case will be handled
+      // directly in the subtype checking algorithm.
+      if (superclassInfo.cls == null ||
+          superclassInfo.cls == coreTypes.objectClass) continue;
+      Class superclass = superclassInfo.cls!;
+      Iterable<Class> subclasses =
+          _getConcreteSubtypes(superclass).where((cls) => cls != superclass);
+      Iterable<InterfaceType> subtypes = subclasses.map(
+          (Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable));
+      for (InterfaceType subtype in subtypes) {
+        interfaceTypeEnvironment._add(subtype);
+        List<DartType>? typeArguments = translator.hierarchy
+            .getTypeArgumentsAsInstanceOf(subtype, superclass);
+        ClassInfo subclassInfo = translator.classInfo[subtype.classNode]!;
+        Map<int, List<DartType>> substitutionMap =
+            subtypeMap[subclassInfo.classId] ??= {};
+        substitutionMap[superclassInfo.classId] = typeArguments ?? const [];
+      }
     }
     return subtypeMap;
   }
 
-  /// Builds the subtype map and pushes it onto the stack.
-  w.ValueType makeSubtypeMap(w.Instructions b) {
-    // Instantiate subtype map constant.
-    Map<int, List<int>> subtypeMap = _buildSubtypeMap();
-    ClassInfo immutableMapInfo =
-        translator.classInfo[translator.immutableMapClass]!;
-    w.ValueType expectedType = immutableMapInfo.nonNullableType;
-    DartType mapAndSetKeyType = translator.coreTypes.intNonNullableRawType;
-    DartType mapValueType = InterfaceType(translator.immutableListClass,
-        Nullability.nonNullable, [mapAndSetKeyType]);
-    List<ConstantMapEntry> entries = subtypeMap.entries.map((mapEntry) {
-      return ConstantMapEntry(
-          IntConstant(mapEntry.key),
-          ListConstant(mapAndSetKeyType,
-              mapEntry.value.map((i) => IntConstant(i)).toList()));
-    }).toList();
-    translator.constants.instantiateConstant(null, b,
-        MapConstant(mapAndSetKeyType, mapValueType, entries), expectedType);
+  List<List<int>> _buildTypeRulesSupers() {
+    List<List<int>> typeRulesSupers = [];
+    for (int i = 0; i < translator.classInfoCollector.nextClassId; i++) {
+      List<int>? superclassIds = typeRules[i]?.keys.toList();
+      if (superclassIds == null) {
+        typeRulesSupers.add(const []);
+      } else {
+        superclassIds.sort();
+        typeRulesSupers.add(superclassIds);
+      }
+    }
+    return typeRulesSupers;
+  }
+
+  List<List<List<DartType>>> _buildTypeRulesSubstitutions() {
+    List<List<List<DartType>>> typeRulesSubstitutions = [];
+    for (int i = 0; i < translator.classInfoCollector.nextClassId; i++) {
+      List<int> supers = typeRulesSupers[i];
+      typeRulesSubstitutions.add(supers.isEmpty ? const [] : []);
+      for (int j = 0; j < supers.length; j++) {
+        int superId = supers[j];
+        typeRulesSubstitutions.last.add(typeRules[i]![superId]!);
+      }
+    }
+    return typeRulesSubstitutions;
+  }
+
+  /// Builds a map of subclasses to the transitive set of superclasses they
+  /// implement.
+  /// TODO(joshualitt): This implementation is just temporary. Eventually we
+  /// should move to a data structure more closely resembling [typeRules].
+  w.ValueType makeTypeRulesSupers(w.Instructions b) {
+    w.ValueType expectedType =
+        translator.classInfo[translator.immutableListClass]!.nonNullableType;
+    DartType listIntType = InterfaceType(translator.immutableListClass,
+        Nullability.nonNullable, [translator.coreTypes.intNonNullableRawType]);
+    List<ListConstant> listIntConstant = [];
+    for (List<int> supers in typeRulesSupers) {
+      listIntConstant.add(ListConstant(
+          listIntType, supers.map((i) => IntConstant(i)).toList()));
+    }
+    DartType listListIntType = InterfaceType(
+        translator.immutableListClass, Nullability.nonNullable, [listIntType]);
+    translator.constants.instantiateConstant(
+        null, b, ListConstant(listListIntType, listIntConstant), expectedType);
+    return expectedType;
+  }
+
+  /// Similar to the above, but provides the substitutions required for each
+  /// supertype.
+  /// TODO(joshualitt): Like [makeTypeRulesSupers], this is just temporary.
+  w.ValueType makeTypeRulesSubstitutions(w.Instructions b) {
+    w.ValueType expectedType =
+        translator.classInfo[translator.immutableListClass]!.nonNullableType;
+    DartType listTypeType = InterfaceType(
+        translator.immutableListClass,
+        Nullability.nonNullable,
+        [translator.typeClass.getThisType(coreTypes, Nullability.nonNullable)]);
+    DartType listListTypeType = InterfaceType(
+        translator.immutableListClass, Nullability.nonNullable, [listTypeType]);
+    DartType listListListTypeType = InterfaceType(translator.immutableListClass,
+        Nullability.nonNullable, [listListTypeType]);
+    List<ListConstant> substitutionsConstantL0 = [];
+    for (List<List<DartType>> substitutionsL1 in typeRulesSubstitutions) {
+      List<ListConstant> substitutionsConstantL1 = [];
+      for (List<DartType> substitutionsL2 in substitutionsL1) {
+        substitutionsConstantL1.add(ListConstant(
+            listTypeType,
+            substitutionsL2.map((t) {
+              // TODO(joshualitt): implement generic functions
+              if (t is FunctionType && isGenericFunction(t)) {
+                return TypeLiteralConstant(DynamicType());
+              } else {
+                return TypeLiteralConstant(t);
+              }
+            }).toList()));
+      }
+      substitutionsConstantL0
+          .add(ListConstant(listListTypeType, substitutionsConstantL1));
+    }
+    translator.constants.instantiateConstant(
+        null,
+        b,
+        ListConstant(listListListTypeType, substitutionsConstantL0),
+        expectedType);
     return expectedType;
   }
 
   bool isGenericFunction(FunctionType type) => type.typeParameters.isNotEmpty;
 
+  bool isGenericFunctionTypeParameter(TypeParameterType type) =>
+      type.parameter.parent == null;
+
   bool _isTypeConstant(DartType type) {
     return type is DynamicType ||
         type is VoidType ||
@@ -113,6 +246,12 @@
       } else {
         return translator.functionTypeClass;
       }
+    } else if (type is TypeParameterType) {
+      if (isGenericFunctionTypeParameter(type)) {
+        return translator.genericFunctionTypeParameterTypeClass;
+      } else {
+        return translator.interfaceTypeParameterTypeClass;
+      }
     }
     throw "Unexpected DartType: $type";
   }
@@ -251,7 +390,9 @@
       TreeNode node) {
     w.Instructions b = codeGen.b;
     if (type is! InterfaceType) {
-      // TODO(askesc): Implement type test for remaining types
+      // TODO(joshualitt): We can enable this after fixing `.runtimeType`.
+      // makeType(codeGen, type);
+      // codeGen.call(translator.isSubtype.reference);
       print("Not implemented: Type test with non-interface type $type"
           " at ${node.location}");
       b.drop();
@@ -284,7 +425,7 @@
       }
     }
     List<Class> concrete = _getConcreteSubtypes(type.classNode).toList();
-    if (type.classNode == translator.coreTypes.functionClass) {
+    if (type.classNode == coreTypes.functionClass) {
       ClassInfo functionInfo = translator.classInfo[translator.functionClass]!;
       translator.ref_test(b, functionInfo);
     } else if (concrete.isEmpty) {
diff --git a/pkg/dev_compiler/tool/ddb b/pkg/dev_compiler/tool/ddb
index 8cfc2e2..799e176 100755
--- a/pkg/dev_compiler/tool/ddb
+++ b/pkg/dev_compiler/tool/ddb
@@ -174,31 +174,28 @@
         mode: ProcessStartMode.inheritStdio, environment: environment);
   }
 
-  Future<void> runDdc(List<String> args) async {
-    if (debug) {
-      // Use unbuilt script.  This only works from a source checkout.
-      var vmServicePort = options.wasParsed('vm-service-port')
-          ? '=${options['vm-service-port']}'
-          : '';
-      var observe =
-          options.wasParsed('vm-service-port') || options['observe'] as bool;
-      args.insertAll(0, [
+  Future<void> runDdc(List<String> ddcArgs) async {
+    var observe =
+        options.wasParsed('vm-service-port') || options['observe'] as bool;
+    var vmServicePort = options.wasParsed('vm-service-port')
+        ? '=${options['vm-service-port']}'
+        : '';
+    var args = <String>[
+      ...?options['compile-vm-options']?.split(' '),
+      if (debug) ...[
         if (observe) ...[
           '--enable-vm-service$vmServicePort',
           '--pause-isolates-on-start',
         ],
         '--enable-asserts',
-        p.join(ddcPath, 'bin', 'dartdevc.dart')
-      ]);
-    } else {
-      // Use built snapshot.
-      args.insertAll(
-          0, [p.join(dartSdk, 'bin', 'snapshots', 'dartdevc.dart.snapshot')]);
-    }
-    var process = await startProcess('DDC', dartBinary, args, <String, String>{
-      if (options['compile-vm-options'] != null)
-        'DART_VM_OPTIONS': options['compile-vm-options'] as String
-    });
+        // Use unbuilt script.  This only works from a source checkout.
+        p.join(ddcPath, 'bin', 'dartdevc.dart'),
+      ] else
+        // Use built snapshot.
+        p.join(dartSdk, 'bin', 'snapshots', 'dartdevc.dart.snapshot'),
+      ...ddcArgs,
+    ];
+    var process = await startProcess('DDC', dartBinary, args);
     if (await process.exitCode != 0) exit(await process.exitCode);
   }
 
diff --git a/runtime/vm/simulator_riscv.cc b/runtime/vm/simulator_riscv.cc
index 73cdb72..2229c31 100644
--- a/runtime/vm/simulator_riscv.cc
+++ b/runtime/vm/simulator_riscv.cc
@@ -383,6 +383,28 @@
 }
 
 void Simulator::Execute() {
+  if (LIKELY(FLAG_trace_sim_after == ULLONG_MAX)) {
+    ExecuteNoTrace();
+  } else {
+    ExecuteTrace();
+  }
+}
+
+void Simulator::ExecuteNoTrace() {
+  while (pc_ != kEndSimulatingPC) {
+    uint16_t parcel = *reinterpret_cast<uint16_t*>(pc_);
+    if (IsCInstruction(parcel)) {
+      CInstr instr(parcel);
+      Interpret(instr);
+    } else {
+      Instr instr(*reinterpret_cast<uint32_t*>(pc_));
+      Interpret(instr);
+    }
+    instret_++;
+  }
+}
+
+void Simulator::ExecuteTrace() {
   while (pc_ != kEndSimulatingPC) {
     uint16_t parcel = *reinterpret_cast<uint16_t*>(pc_);
     if (IsCInstruction(parcel)) {
@@ -482,6 +504,7 @@
   }
 }
 
+DART_FORCE_INLINE
 void Simulator::Interpret(Instr instr) {
   switch (instr.opcode()) {
     case LUI:
@@ -552,6 +575,7 @@
   }
 }
 
+DART_FORCE_INLINE
 void Simulator::Interpret(CInstr instr) {
   switch (instr.opcode()) {
     case C_LWSP: {
@@ -812,21 +836,25 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretLUI(Instr instr) {
   set_xreg(instr.rd(), sign_extend(instr.utype_imm()));
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretAUIPC(Instr instr) {
   set_xreg(instr.rd(), pc_ + sign_extend(instr.utype_imm()));
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretJAL(Instr instr) {
   set_xreg(instr.rd(), pc_ + instr.length());
   pc_ += sign_extend(instr.jtype_imm());
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretJALR(Instr instr) {
   uintx_t base = get_xreg(instr.rs1());
   uintx_t offset = static_cast<uintx_t>(instr.itype_imm());
@@ -834,6 +862,7 @@
   pc_ = base + offset;
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretBRANCH(Instr instr) {
   switch (instr.funct3()) {
     case BEQ:
@@ -887,6 +916,7 @@
   }
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretLOAD(Instr instr) {
   uintx_t addr = get_xreg(instr.rs1()) + instr.itype_imm();
   switch (instr.funct3()) {
@@ -919,6 +949,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretLOADFP(Instr instr) {
   uintx_t addr = get_xreg(instr.rs1()) + instr.itype_imm();
   switch (instr.funct3()) {
@@ -934,6 +965,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretSTORE(Instr instr) {
   uintx_t addr = get_xreg(instr.rs1()) + instr.stype_imm();
   switch (instr.funct3()) {
@@ -957,6 +989,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretSTOREFP(Instr instr) {
   uintx_t addr = get_xreg(instr.rs1()) + instr.stype_imm();
   switch (instr.funct3()) {
@@ -972,6 +1005,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOPIMM(Instr instr) {
   switch (instr.funct3()) {
     case ADDI:
@@ -1018,6 +1052,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOPIMM32(Instr instr) {
   switch (instr.funct3()) {
     case ADDI: {
@@ -1049,6 +1084,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP(Instr instr) {
   switch (instr.funct7()) {
     case 0:
@@ -1065,6 +1101,7 @@
   }
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP_0(Instr instr) {
   switch (instr.funct3()) {
     case ADD:
@@ -1247,6 +1284,7 @@
 }
 #endif  // XLEN >= 64
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP_MULDIV(Instr instr) {
   switch (instr.funct3()) {
     case MUL:
@@ -1280,6 +1318,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP_SUB(Instr instr) {
   switch (instr.funct3()) {
     case ADD:
@@ -1296,6 +1335,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP32(Instr instr) {
   switch (instr.funct7()) {
 #if XLEN >= 64
@@ -1314,6 +1354,7 @@
   }
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP32_0(Instr instr) {
   switch (instr.funct3()) {
 #if XLEN >= 64
@@ -1342,6 +1383,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP32_SUB(Instr instr) {
   switch (instr.funct3()) {
 #if XLEN >= 64
@@ -1364,6 +1406,7 @@
   pc_ += instr.length();
 }
 
+DART_FORCE_INLINE
 void Simulator::InterpretOP32_MULDIV(Instr instr) {
   switch (instr.funct3()) {
 #if XLEN >= 64
diff --git a/runtime/vm/simulator_riscv.h b/runtime/vm/simulator_riscv.h
index 9a5ca23..d49fed9 100644
--- a/runtime/vm/simulator_riscv.h
+++ b/runtime/vm/simulator_riscv.h
@@ -325,6 +325,8 @@
 
   // Executes RISC-V instructions until the PC reaches kEndSimulatingPC.
   void Execute();
+  void ExecuteNoTrace();
+  void ExecuteTrace();
 
   // Returns true if tracing of executed instructions is enabled.
   bool IsTracingExecution() const;
diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart
index 2e16f69..08f2006 100644
--- a/sdk/lib/_internal/wasm/lib/class_id.dart
+++ b/sdk/lib/_internal/wasm/lib/class_id.dart
@@ -38,6 +38,10 @@
   external static int get cidFunctionType;
   @pragma("wasm:class-id", "dart.core#_GenericFunctionType")
   external static int get cidGenericFunctionType;
+  @pragma("wasm:class-id", "dart.core#_GenericFunctionTypeParameterType")
+  external static int get cidGenericFunctionTypeParameterType;
+  @pragma("wasm:class-id", "dart.core#_InterfaceTypeParameterType")
+  external static int get cidInterfaceTypeParameterType;
 
   // Dummy, only used by VM-specific hash table code.
   static final int numPredefinedCids = 1;
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
index 046c15b..fcee927 100644
--- a/sdk/lib/_internal/wasm/lib/type.dart
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -26,14 +26,18 @@
   bool get isNull => _testID(ClassID.cidNullType);
   bool get isFutureOr => _testID(ClassID.cidFutureOrType);
   bool get isInterface => _testID(ClassID.cidInterfaceType);
+  bool get isInterfaceTypeParameterType =>
+      _testID(ClassID.cidInterfaceTypeParameterType);
   bool get isFunction => _testID(ClassID.cidFunctionType);
   bool get isGenericFunction => _testID(ClassID.cidGenericFunctionType);
 
   T as<T>() => unsafeCast<T>(this);
 
   _Type get asNonNullable => isNullable ? _asNonNullable : this;
+  _Type get asNullable => isNullable ? this : _asNullable;
 
   _Type get _asNonNullable;
+  _Type get _asNullable;
 
   @override
   bool operator ==(Object other) => ClassID.getID(this) == ClassID.getID(other);
@@ -49,6 +53,10 @@
   @override
   _Type get _asNonNullable => this;
 
+  /// Never? normalizes to Null.
+  @override
+  _Type get _asNullable => const _NullType();
+
   @override
   String toString() => 'Never';
 }
@@ -61,6 +69,9 @@
   _Type get _asNonNullable => throw '`dynamic` type is always nullable.';
 
   @override
+  _Type get _asNullable => this;
+
+  @override
   String toString() => 'dynamic';
 }
 
@@ -72,6 +83,9 @@
   _Type get _asNonNullable => throw '`void` type is always nullable.';
 
   @override
+  _Type get _asNullable => this;
+
+  @override
   String toString() => 'void';
 }
 
@@ -83,9 +97,52 @@
   _Type get _asNonNullable => const _NeverType();
 
   @override
+  _Type get _asNullable => this;
+
+  @override
   String toString() => 'Null';
 }
 
+/// Because Interface type parameters are fundamentally different from Generic
+/// function type parameters, we are keeping these classes separate for the time
+/// being.
+@pragma("wasm:entry-point")
+class _InterfaceTypeParameterType extends _Type {
+  final int environmentIndex;
+
+  const _InterfaceTypeParameterType(super.isNullable, this.environmentIndex);
+
+  @override
+  _Type get _asNonNullable =>
+      throw 'Type parameter should have been substituted already.';
+
+  @override
+  _Type get _asNullable =>
+      throw 'Type parameter should have been substituted already.';
+
+  @override
+  String toString() => '$environmentIndex';
+}
+
+@pragma("wasm:entry-point")
+class _GenericFunctionTypeParameterType extends _Type {
+  final int environmentIndex;
+
+  const _GenericFunctionTypeParameterType(
+      super.isNullable, this.environmentIndex);
+
+  @override
+  _Type get _asNonNullable =>
+      throw 'Type parameter should have been substituted already..';
+
+  @override
+  _Type get _asNullable =>
+      throw 'Type parameter should have been substituted already.';
+
+  @override
+  String toString() => '$environmentIndex';
+}
+
 @pragma("wasm:entry-point")
 class _FutureOrType extends _Type {
   final _Type typeArgument;
@@ -103,6 +160,9 @@
   }
 
   @override
+  _Type get _asNullable => _FutureOrType(true, typeArgument);
+
+  @override
   bool operator ==(Object o) {
     if (!(super == o)) return false;
     _FutureOrType other = unsafeCast<_FutureOrType>(o);
@@ -142,6 +202,9 @@
   _Type get _asNonNullable => _InterfaceType(classId, false, typeArguments);
 
   @override
+  _Type get _asNullable => _InterfaceType(classId, true, typeArguments);
+
+  @override
   bool operator ==(Object o) {
     if (!(super == o)) return false;
     _InterfaceType other = unsafeCast<_InterfaceType>(o);
@@ -234,6 +297,10 @@
   _Type get _asNonNullable => _FunctionType(returnType, positionalParameters,
       requiredParameterCount, namedParameters, false);
 
+  @override
+  _Type get _asNullable => _FunctionType(returnType, positionalParameters,
+      requiredParameterCount, namedParameters, true);
+
   bool operator ==(Object o) {
     if (!(super == o)) return false;
     _FunctionType other = unsafeCast<_FunctionType>(o);
@@ -305,19 +372,63 @@
   _Type get _asNonNullable => throw 'unimplemented';
 
   @override
+  _Type get _asNullable => throw 'unimplemented';
+
+  @override
   String toString() => 'GenericFunctionType';
 }
 
-external Map<int, List<int>> _getSubtypeMap();
+external List<List<int>> _getTypeRulesSupers();
+external List<List<List<_Type>>> _getTypeRulesSubstitutions();
+
+class _Environment {
+  List<List<_Type>> scopes = [];
+
+  _Environment();
+
+  factory _Environment.from(List<_Type> initialScope) {
+    final env = _Environment();
+    env.push(initialScope);
+    return env;
+  }
+
+  void push(List<_Type> scope) => scopes.add(scope);
+
+  void pop() => scopes.removeLast();
+
+  _Type _substituteTypeParameter(bool declaredNullable, _Type type) {
+    // If the type parameter is non-nullable, or the substitution type is
+    // nullable, then just return the substitution type. Otherwise, we return
+    // [type] as nullable.
+    // Note: This will throw if the required nullability is impossible to
+    // generate.
+    if (!declaredNullable || type.isNullable) {
+      return type;
+    }
+    return type.asNullable;
+  }
+
+  _Type lookup(_InterfaceTypeParameterType typeParameter) {
+    // Lookup `InterfaceType` parameters in the top environment.
+    // TODO(joshualitt): When we implement generic functions be sure to keep the
+    // environments distinct.
+    return _substituteTypeParameter(
+        typeParameter.isNullable, scopes.last[typeParameter.environmentIndex]);
+  }
+}
 
 class _TypeUniverse {
-  /// 'Map' of classId to range of subclasses.
-  final Map<int, List<int>> _subtypeMap;
+  /// 'Map' of classId to the transitive set of super classes it implements.
+  final List<List<int>> typeRulesSupers;
 
-  const _TypeUniverse._(this._subtypeMap);
+  /// 'Map' of classId, and super offset(from [typeRulesSupers]) to a list of
+  /// type substitutions.
+  final List<List<List<_Type>>> typeRulesSubstitutions;
+
+  const _TypeUniverse._(this.typeRulesSupers, this.typeRulesSubstitutions);
 
   factory _TypeUniverse.create() {
-    return _TypeUniverse._(_getSubtypeMap());
+    return _TypeUniverse._(_getTypeRulesSupers(), _getTypeRulesSubstitutions());
   }
 
   bool isSpecificInterfaceType(_Type t, int classId) {
@@ -341,27 +452,55 @@
   bool isFunctionType(_Type t) =>
       isSpecificInterfaceType(t, ClassID.cidFunction);
 
-  bool isInterfaceSubtype(_InterfaceType s, _InterfaceType t) {
-    int sId = s.classId;
-    int tId = t.classId;
-    if (sId == tId) {
-      assert(s.typeArguments.length == t.typeArguments.length);
-      for (int i = 0; i < s.typeArguments.length; i++) {
-        if (!isSubtype(s.typeArguments[i], t.typeArguments[i])) {
-          return false;
-        }
+  bool areTypeArgumentsSubtypes(List<_Type> sArgs, _Environment? sEnv,
+      List<_Type> tArgs, _Environment? tEnv) {
+    assert(sArgs.length == tArgs.length);
+    for (int i = 0; i < sArgs.length; i++) {
+      if (!isSubtype(sArgs[i], sEnv, tArgs[i], tEnv)) {
+        return false;
       }
-      return true;
     }
-    List<int>? subtypes = _subtypeMap[tId];
-    if (subtypes == null) return false;
-    if (!subtypes.contains(sId)) return false;
-    // TODO(joshualitt): Compare type arguments.
     return true;
   }
 
-  bool isFunctionSubtype(_FunctionType s, _FunctionType t) {
-    if (!isSubtype(s.returnType, t.returnType)) return false;
+  bool isInterfaceSubtype(_InterfaceType s, _Environment? sEnv,
+      _InterfaceType t, _Environment? tEnv) {
+    int sId = s.classId;
+    int tId = t.classId;
+
+    // If we have the same class, simply compare type arguments.
+    if (sId == tId) {
+      return areTypeArgumentsSubtypes(
+          s.typeArguments, sEnv, t.typeArguments, tEnv);
+    }
+
+    // Otherwise, check if [s] is a subtype of [t], and if it is then compare
+    // [s]'s type substitutions with [t]'s type arguments.
+    List<int> sSupers = typeRulesSupers[sId];
+    if (sSupers.isEmpty) return false;
+    int sSuperIndexOfT = sSupers.indexOf(tId);
+    if (sSuperIndexOfT == -1) return false;
+    assert(sSuperIndexOfT < typeRulesSubstitutions[sId].length);
+
+    List<_Type> substitutions = typeRulesSubstitutions[sId][sSuperIndexOfT];
+
+    // If [sEnv] is null, then create a new environment. Otherwise, we are doing
+    // a recursive type check, so extend the existing environment with [s]'s
+    // type arguments.
+    if (sEnv == null) {
+      sEnv = _Environment.from(s.typeArguments);
+    } else {
+      sEnv.push(s.typeArguments);
+    }
+    bool result =
+        areTypeArgumentsSubtypes(substitutions, sEnv, t.typeArguments, tEnv);
+    sEnv.pop();
+    return result;
+  }
+
+  bool isFunctionSubtype(_FunctionType s, _Environment? sEnv, _FunctionType t,
+      _Environment? tEnv) {
+    if (!isSubtype(s.returnType, sEnv, t.returnType, tEnv)) return false;
 
     // Check [s] does not have more required positional arguments than [t].
     int sRequiredCount = s.requiredParameterCount;
@@ -385,7 +524,7 @@
     for (int i = 0; i < tPositionalLength; i++) {
       _Type sParameter = sPositional[i];
       _Type tParameter = tPositional[i];
-      if (!isSubtype(tParameter, sParameter)) {
+      if (!isSubtype(tParameter, tEnv, sParameter, sEnv)) {
         return false;
       }
     }
@@ -414,7 +553,8 @@
         }
         bool tIsRequired = tNamedParameter.isRequired;
         if (sIsRequired && !tIsRequired) return false;
-        if (!isSubtype(tNamedParameter.type, sNamedParameter.type)) {
+        if (!isSubtype(
+            tNamedParameter.type, tEnv, sNamedParameter.type, sEnv)) {
           return false;
         }
         break;
@@ -428,7 +568,7 @@
 
   // Subtype check based off of sdk/lib/_internal/js_runtime/lib/rti.dart.
   // Returns true if [s] is a subtype of [t], false otherwise.
-  bool isSubtype(_Type s, _Type t) {
+  bool isSubtype(_Type s, _Environment? sEnv, _Type t, _Environment? tEnv) {
     // Reflexivity:
     if (identical(s, t)) return true;
 
@@ -442,7 +582,11 @@
     if (isBottomType(s)) return true;
 
     // Left Type Variable Bound 1:
-    // TODO(joshualitt): Implement.
+    // TODO(joshualitt): Implement for generic function type parameters.
+    if (s.isInterfaceTypeParameterType) {
+      return isSubtype(
+          sEnv!.lookup(s.as<_InterfaceTypeParameterType>()), sEnv, t, tEnv);
+    }
 
     // Left Null:
     // TODO(joshualitt): Combine with 'Right Null', and this can just be:
@@ -459,7 +603,7 @@
     // Left FutureOr:
     if (s.isFutureOr) {
       _FutureOrType sFutureOr = s.as<_FutureOrType>();
-      if (!isSubtype(sFutureOr.typeArgument, t)) {
+      if (!isSubtype(sFutureOr.typeArgument, sEnv, t, tEnv)) {
         return false;
       }
       return _isSubtype(sFutureOr.asFuture, t);
@@ -467,7 +611,7 @@
 
     // Left Nullable:
     if (s.isNullable) {
-      return t.isNullable && isSubtype(s.asNonNullable, t);
+      return t.isNullable && isSubtype(s.asNonNullable, sEnv, t, tEnv);
     }
 
     // Type Variable Reflexivity 1 is subsumed by Reflexivity and therefore
@@ -478,15 +622,15 @@
     // Right FutureOr:
     if (t.isFutureOr) {
       _FutureOrType tFutureOr = t.as<_FutureOrType>();
-      if (isSubtype(s, tFutureOr.typeArgument)) {
+      if (isSubtype(s, sEnv, tFutureOr.typeArgument, tEnv)) {
         return true;
       }
-      return isSubtype(s, tFutureOr.asFuture);
+      return isSubtype(s, sEnv, tFutureOr.asFuture, tEnv);
     }
 
     // Right Nullable:
     if (t.isNullable) {
-      return isSubtype(s, t.asNonNullable);
+      return isSubtype(s, sEnv, t.asNonNullable, tEnv);
     }
 
     // Left Promoted Variable does not apply at runtime.
@@ -506,13 +650,15 @@
     }
 
     if (s.isFunction && t.isFunction) {
-      return isFunctionSubtype(s.as<_FunctionType>(), t.as<_FunctionType>());
+      return isFunctionSubtype(
+          s.as<_FunctionType>(), sEnv, t.as<_FunctionType>(), tEnv);
     }
 
     // Interface Compositionality + Super-Interface:
     if (s.isInterface &&
         t.isInterface &&
-        isInterfaceSubtype(s.as<_InterfaceType>(), t.as<_InterfaceType>())) {
+        isInterfaceSubtype(
+            s.as<_InterfaceType>(), sEnv, t.as<_InterfaceType>(), tEnv)) {
       return true;
     }
     return false;
@@ -523,5 +669,6 @@
 
 @pragma("wasm:entry-point")
 bool _isSubtype(Object? s, _Type t) {
-  return _typeUniverse.isSubtype(unsafeCast<_Type>(s.runtimeType), t);
+  return _typeUniverse.isSubtype(
+      unsafeCast<_Type>(s.runtimeType), null, t, null);
 }
diff --git a/sdk/lib/math/math.dart b/sdk/lib/math/math.dart
index d9e3f75..118d95d 100644
--- a/sdk/lib/math/math.dart
+++ b/sdk/lib/math/math.dart
@@ -12,7 +12,7 @@
 /// ## Random
 /// [Random] is a generator of [bool], [int] or [double] values.
 /// ```dart
-/// var intValue = Random().nextInt(10); // Value is >= 0.0 and < 1.0.
+/// var intValue = Random().nextInt(10); // Value is >= 0 and < 10.
 /// var doubleValue = Random().nextDouble(); // Value is >= 0.0 and < 1.0.
 /// var boolValue = Random().nextBool(); // true or false, with equal chance.
 /// ```
diff --git a/tools/VERSION b/tools/VERSION
index d6a09da..aac23b3 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 198
+PRERELEASE 199
 PRERELEASE_PATCH 0
\ No newline at end of file