Version 2.15.0-28.0.dev

Merge commit '16e4709922a0abf92240e89ebe573623a01c83aa' into 'dev'
diff --git a/benchmarks/EventLoopLatencyRegexp/dart/EventLoopLatencyRegexp.dart b/benchmarks/EventLoopLatencyRegexp/dart/EventLoopLatencyRegexp.dart
index 5ffe53f..40f44a5 100644
--- a/benchmarks/EventLoopLatencyRegexp/dart/EventLoopLatencyRegexp.dart
+++ b/benchmarks/EventLoopLatencyRegexp/dart/EventLoopLatencyRegexp.dart
@@ -5,7 +5,7 @@
 import 'dart:isolate';
 
 import 'regexp_benchmark.dart';
-import '../../EventLoopLatencyJson/dart/latency.dart';
+import 'latency.dart';
 
 main() async {
   final exitPort = ReceivePort();
diff --git a/benchmarks/EventLoopLatencyRegexp/dart/latency.dart b/benchmarks/EventLoopLatencyRegexp/dart/latency.dart
new file mode 100644
index 0000000..5da9f4e
--- /dev/null
+++ b/benchmarks/EventLoopLatencyRegexp/dart/latency.dart
@@ -0,0 +1,135 @@
+// Copyright (c) 2020, 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.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:math' as math;
+import 'dart:typed_data';
+
+/// Measures event loop responsiveness.
+///
+/// Schedules new timer events, [tickDuration] in the future, and measures how
+/// long it takes for these events to actually arrive.
+///
+/// Runs [numberOfTicks] times before completing with [EventLoopLatencyStats].
+Future<EventLoopLatencyStats> measureEventLoopLatency(
+    Duration tickDuration, int numberOfTicks) {
+  final completer = Completer<EventLoopLatencyStats>();
+
+  final tickDurationInUs = tickDuration.inMicroseconds;
+  final buffer = _TickLatencies(numberOfTicks);
+  final sw = Stopwatch()..start();
+  int lastTimestamp = 0;
+
+  void trigger() {
+    final int currentTimestamp = sw.elapsedMicroseconds;
+
+    // Every tick we missed to schedule we'll add with difference to when we
+    // would've scheduled it and when we became responsive again.
+    bool done = false;
+    while (!done && lastTimestamp < (currentTimestamp - tickDurationInUs)) {
+      done = !buffer.add(currentTimestamp - lastTimestamp - tickDurationInUs);
+      lastTimestamp += tickDurationInUs;
+    }
+
+    if (!done) {
+      lastTimestamp = currentTimestamp;
+      Timer(tickDuration, trigger);
+    } else {
+      completer.complete(buffer.makeStats());
+    }
+  }
+
+  Timer(tickDuration, trigger);
+
+  return completer.future;
+}
+
+/// Result of the event loop latency measurement.
+class EventLoopLatencyStats {
+  /// Minimum latency between scheduling a tick and it's arrival (in ms).
+  final double minLatency;
+
+  /// Average latency between scheduling a tick and it's arrival (in ms).
+  final double avgLatency;
+
+  /// Maximum latency between scheduling a tick and it's arrival (in ms).
+  final double maxLatency;
+
+  /// The 50th percentile (median) (in ms).
+  final double percentile50th;
+
+  /// The 90th percentile (in ms).
+  final double percentile90th;
+
+  /// The 95th percentile (in ms).
+  final double percentile95th;
+
+  /// The 99th percentile (in ms).
+  final double percentile99th;
+
+  /// The maximum RSS of the process.
+  final int maxRss;
+
+  EventLoopLatencyStats(
+      this.minLatency,
+      this.avgLatency,
+      this.maxLatency,
+      this.percentile50th,
+      this.percentile90th,
+      this.percentile95th,
+      this.percentile99th,
+      this.maxRss);
+
+  void report(String name) {
+    print('$name.Min(RunTimeRaw): $minLatency ms.');
+    print('$name.Avg(RunTimeRaw): $avgLatency ms.');
+    print('$name.Percentile50(RunTimeRaw): $percentile50th ms.');
+    print('$name.Percentile90(RunTimeRaw): $percentile90th ms.');
+    print('$name.Percentile95(RunTimeRaw): $percentile95th ms.');
+    print('$name.Percentile99(RunTimeRaw): $percentile99th ms.');
+    print('$name.Max(RunTimeRaw): $maxLatency ms.');
+    print('$name.MaxRss(MemoryUse): $maxRss');
+  }
+}
+
+/// Accumulates tick latencies and makes statistics for it.
+class _TickLatencies {
+  final Uint64List _timestamps;
+  int _index = 0;
+
+  _TickLatencies(int numberOfTicks) : _timestamps = Uint64List(numberOfTicks);
+
+  /// Returns `true` while the buffer has not been filled yet.
+  bool add(int latencyInUs) {
+    _timestamps[_index++] = latencyInUs;
+    return _index < _timestamps.length;
+  }
+
+  EventLoopLatencyStats makeStats() {
+    if (_index != _timestamps.length) {
+      throw 'Buffer has not been fully filled yet.';
+    }
+
+    _timestamps.sort();
+    final length = _timestamps.length;
+    final double avg = _timestamps.fold(0, (int a, int b) => a + b) / length;
+    final int min = _timestamps.fold(0x7fffffffffffffff, math.min);
+    final int max = _timestamps.fold(0, math.max);
+    final percentile50th = _timestamps[50 * length ~/ 100];
+    final percentile90th = _timestamps[90 * length ~/ 100];
+    final percentile95th = _timestamps[95 * length ~/ 100];
+    final percentile99th = _timestamps[99 * length ~/ 100];
+
+    return EventLoopLatencyStats(
+        min / 1000,
+        avg / 1000,
+        max / 1000,
+        percentile50th / 1000,
+        percentile90th / 1000,
+        percentile95th / 1000,
+        percentile99th / 1000,
+        ProcessInfo.maxRss);
+  }
+}
diff --git a/benchmarks/EventLoopLatencyRegexp/dart2/EventLoopLatencyRegexp.dart b/benchmarks/EventLoopLatencyRegexp/dart2/EventLoopLatencyRegexp.dart
index dec8cb2..86ef5bc 100644
--- a/benchmarks/EventLoopLatencyRegexp/dart2/EventLoopLatencyRegexp.dart
+++ b/benchmarks/EventLoopLatencyRegexp/dart2/EventLoopLatencyRegexp.dart
@@ -7,7 +7,7 @@
 import 'dart:isolate';
 
 import 'json_benchmark.dart';
-import '../../EventLoopLatencyJson/dart2/latency.dart';
+import 'latency.dart';
 
 main() async {
   final exitPort = ReceivePort();
diff --git a/benchmarks/EventLoopLatencyRegexp/dart2/latency.dart b/benchmarks/EventLoopLatencyRegexp/dart2/latency.dart
new file mode 100644
index 0000000..44ecae6
--- /dev/null
+++ b/benchmarks/EventLoopLatencyRegexp/dart2/latency.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2020, 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.
+
+// @dart=2.9
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:math' as math;
+import 'dart:typed_data';
+
+/// Measures event loop responsiveness.
+///
+/// Schedules new timer events, [tickDuration] in the future, and measures how
+/// long it takes for these events to actually arrive.
+///
+/// Runs [numberOfTicks] times before completing with [EventLoopLatencyStats].
+Future<EventLoopLatencyStats> measureEventLoopLatency(
+    Duration tickDuration, int numberOfTicks) {
+  final completer = Completer<EventLoopLatencyStats>();
+
+  final tickDurationInUs = tickDuration.inMicroseconds;
+  final buffer = _TickLatencies(numberOfTicks);
+  final sw = Stopwatch()..start();
+  int lastTimestamp = 0;
+
+  void trigger() {
+    final int currentTimestamp = sw.elapsedMicroseconds;
+
+    // Every tick we missed to schedule we'll add with difference to when we
+    // would've scheduled it and when we became responsive again.
+    bool done = false;
+    while (!done && lastTimestamp < (currentTimestamp - tickDurationInUs)) {
+      done = !buffer.add(currentTimestamp - lastTimestamp - tickDurationInUs);
+      lastTimestamp += tickDurationInUs;
+    }
+
+    if (!done) {
+      lastTimestamp = currentTimestamp;
+      Timer(tickDuration, trigger);
+    } else {
+      completer.complete(buffer.makeStats());
+    }
+  }
+
+  Timer(tickDuration, trigger);
+
+  return completer.future;
+}
+
+/// Result of the event loop latency measurement.
+class EventLoopLatencyStats {
+  /// Minimum latency between scheduling a tick and it's arrival (in ms).
+  final double minLatency;
+
+  /// Average latency between scheduling a tick and it's arrival (in ms).
+  final double avgLatency;
+
+  /// Maximum latency between scheduling a tick and it's arrival (in ms).
+  final double maxLatency;
+
+  /// The 50th percentile (median) (in ms).
+  final double percentile50th;
+
+  /// The 90th percentile (in ms).
+  final double percentile90th;
+
+  /// The 95th percentile (in ms).
+  final double percentile95th;
+
+  /// The 99th percentile (in ms).
+  final double percentile99th;
+
+  /// The maximum RSS of the process.
+  final int maxRss;
+
+  EventLoopLatencyStats(
+      this.minLatency,
+      this.avgLatency,
+      this.maxLatency,
+      this.percentile50th,
+      this.percentile90th,
+      this.percentile95th,
+      this.percentile99th,
+      this.maxRss);
+
+  void report(String name) {
+    print('$name.Min(RunTimeRaw): $minLatency ms.');
+    print('$name.Avg(RunTimeRaw): $avgLatency ms.');
+    print('$name.Percentile50(RunTimeRaw): $percentile50th ms.');
+    print('$name.Percentile90(RunTimeRaw): $percentile90th ms.');
+    print('$name.Percentile95(RunTimeRaw): $percentile95th ms.');
+    print('$name.Percentile99(RunTimeRaw): $percentile99th ms.');
+    print('$name.Max(RunTimeRaw): $maxLatency ms.');
+    print('$name.MaxRss(MemoryUse): $maxRss');
+  }
+}
+
+/// Accumulates tick latencies and makes statistics for it.
+class _TickLatencies {
+  final Uint64List _timestamps;
+  int _index = 0;
+
+  _TickLatencies(int numberOfTicks) : _timestamps = Uint64List(numberOfTicks);
+
+  /// Returns `true` while the buffer has not been filled yet.
+  bool add(int latencyInUs) {
+    _timestamps[_index++] = latencyInUs;
+    return _index < _timestamps.length;
+  }
+
+  EventLoopLatencyStats makeStats() {
+    if (_index != _timestamps.length) {
+      throw 'Buffer has not been fully filled yet.';
+    }
+
+    _timestamps.sort();
+    final length = _timestamps.length;
+    final double avg = _timestamps.fold(0, (int a, int b) => a + b) / length;
+    final int min = _timestamps.fold(0x7fffffffffffffff, math.min);
+    final int max = _timestamps.fold(0, math.max);
+    final percentile50th = _timestamps[50 * length ~/ 100];
+    final percentile90th = _timestamps[90 * length ~/ 100];
+    final percentile95th = _timestamps[95 * length ~/ 100];
+    final percentile99th = _timestamps[99 * length ~/ 100];
+
+    return EventLoopLatencyStats(
+        min / 1000,
+        avg / 1000,
+        max / 1000,
+        percentile50th / 1000,
+        percentile90th / 1000,
+        percentile95th / 1000,
+        percentile99th / 1000,
+        ProcessInfo.maxRss);
+  }
+}
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 a9bc050..d203ef9 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
@@ -6,6 +6,7 @@
 import 'package:analysis_server/lsp_protocol/protocol_special.dart';
 import 'package:analysis_server/protocol/protocol_generated.dart'
     hide AnalysisGetNavigationParams;
+import 'package:analysis_server/src/domains/analysis/macro_files.dart';
 import 'package:analysis_server/src/lsp/handlers/handlers.dart';
 import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
 import 'package:analysis_server/src/lsp/mapping.dart';
@@ -15,6 +16,7 @@
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart';
+import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
 import 'package:analyzer_plugin/utilities/navigation/navigation_dart.dart';
 import 'package:collection/collection.dart';
 
@@ -54,8 +56,10 @@
     final result = await server.getResolvedUnit(path);
     final unit = result?.unit;
     if (result?.state == ResultState.VALID && unit != null) {
-      computeDartNavigation(
-          server.resourceProvider, collector, unit, offset, 0);
+      computeDartNavigation(server.resourceProvider, collector, unit, offset, 0,
+          analyzerConverter: AnalyzerConverter(
+              locationProvider: MacroElementLocationProvider(
+                  MacroFiles(server.resourceProvider))));
       collector.createRegions();
     }
 
diff --git a/pkg/analysis_server/test/lsp/definition_test.dart b/pkg/analysis_server/test/lsp/definition_test.dart
index f15c592..083158c 100644
--- a/pkg/analysis_server/test/lsp/definition_test.dart
+++ b/pkg/analysis_server/test/lsp/definition_test.dart
@@ -188,6 +188,71 @@
     );
   }
 
+  Future<void> test_macro_simpleIdentifier_getter() async {
+    final macroAnnotationsContents = '''
+library analyzer.macro.annotations;
+const observable = 0;
+''';
+
+    final mainContents = '''
+import 'macro_annotations.dart';
+
+class A {
+  @observable
+  int _foo = 0;
+}
+
+void f(A a) {
+  a.[[foo^]];
+}
+''';
+
+    final combinedContents = r'''
+import 'macro_annotations.dart';
+
+class A {
+  @observable
+  int _foo = 0;
+
+  int get [[foo]] => _foo;
+
+  set foo(int val) {
+    print('Setting foo to ${val}');
+    _foo = val;
+  }
+}
+
+void f(A a) {
+  a.[[foo^]];
+}
+''';
+
+    final macroAnnotationsFileUri =
+        Uri.file(join(projectFolderPath, 'lib', 'macro_annotations.dart'));
+    final pubspecFileUri = Uri.file(join(projectFolderPath, 'pubspec.yaml'));
+    final combinedFileUri = Uri.file(join(projectFolderPath, '.dart_tool',
+        'analyzer', 'macro', 'lib', 'main.dart'));
+
+    await initialize(
+        textDocumentCapabilities:
+            withLocationLinkSupport(emptyTextDocumentClientCapabilities));
+    await openFile(pubspecFileUri, '');
+    await openFile(
+        macroAnnotationsFileUri, withoutMarkers(macroAnnotationsContents));
+    await openFile(mainFileUri, withoutMarkers(mainContents));
+    final res = await getDefinitionAsLocationLinks(
+        mainFileUri, positionFromMarker(mainContents));
+
+    expect(res, hasLength(1));
+    final loc = res.single;
+    expect(loc.originSelectionRange, equals(rangeFromMarkers(mainContents)));
+    expect(loc.targetUri, equals(combinedFileUri.toString()));
+
+    final getFooRange = rangesFromMarkers(combinedContents)[0];
+    expect(loc.targetRange, equals(getFooRange));
+    expect(loc.targetSelectionRange, equals(getFooRange));
+  }
+
   Future<void> test_nonDartFile() async {
     newFile(pubspecFilePath, content: simplePubspecContent);
     await initialize();
diff --git a/pkg/front_end/test/generated_files_up_to_date_git_test.dart b/pkg/front_end/test/generated_files_up_to_date_git_test.dart
index 0800570..cb38e6a 100644
--- a/pkg/front_end/test/generated_files_up_to_date_git_test.dart
+++ b/pkg/front_end/test/generated_files_up_to_date_git_test.dart
@@ -13,7 +13,9 @@
     as generateDirectParserAstHelper;
 import "parser_test_listener_creator.dart" as generateParserTestListener;
 import "parser_test_parser_creator.dart" as generateParserTestParser;
+import '../tool/ast_model.dart';
 import '../tool/generate_ast_equivalence.dart' as generateAstEquivalence;
+import '../tool/generate_ast_coverage.dart' as generateAstCoverage;
 import 'utils/io_utils.dart' show computeRepoDirUri;
 
 final Uri repoDir = computeRepoDirUri();
@@ -24,7 +26,9 @@
   directParserAstHelper();
   parserTestListener();
   parserTestParser();
-  await astEquivalence();
+  AstModel astModel = await deriveAstModel(repoDir);
+  await astEquivalence(astModel);
+  await astCoverage(astModel);
 }
 
 void parserTestParser() {
@@ -50,14 +54,22 @@
       "dart pkg/front_end/tool/_fasta/direct_parser_ast_helper_creator.dart");
 }
 
-Future<void> astEquivalence() async {
+Future<void> astEquivalence(AstModel astModel) async {
   Uri generatedFile = generateAstEquivalence.computeEquivalenceUri(repoDir);
   String generated =
-      await generateAstEquivalence.generateAstEquivalence(repoDir);
+      await generateAstEquivalence.generateAstEquivalence(repoDir, astModel);
   check(generated, generatedFile,
       "dart pkg/front_end/tool/generate_ast_equivalence.dart");
 }
 
+Future<void> astCoverage(AstModel astModel) async {
+  Uri generatedFile = generateAstCoverage.computeCoverageUri(repoDir);
+  String generated =
+      await generateAstCoverage.generateAstCoverage(repoDir, astModel);
+  check(generated, generatedFile,
+      "dart pkg/front_end/tool/generate_ast_coverage.dart");
+}
+
 void experimentalFlags() {
   {
     Uri generatedFile =
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 201c0e11..1e568ba 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -262,6 +262,7 @@
 coverage
 cr
 creator
+creators
 criterion
 cross
 cruft
@@ -457,6 +458,7 @@
 firsts
 fishy
 fishythefish
+fits
 fixnum
 fleshed
 float32
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index 14694ef..6ec4b57 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -548,6 +548,7 @@
 insufficient
 intdiv
 interactive
+interchangeable
 interested
 internet
 interpolate
diff --git a/pkg/front_end/tool/ast_model.dart b/pkg/front_end/tool/ast_model.dart
index 14f981f..74d8cf2 100644
--- a/pkg/front_end/tool/ast_model.dart
+++ b/pkg/front_end/tool/ast_model.dart
@@ -55,6 +55,20 @@
   'PrimitiveConstant',
 };
 
+/// Names of inner [Node] classes that are used as interfaces for (generally)
+/// interchangeable classes.
+///
+/// For instance, when [Expression] is used as the field type, any subtype of
+/// [Expression] can be used to populate the field.
+const Set<String> _interchangeableClasses = const {
+  'Member',
+  'Statement',
+  'Expression',
+  'Constant',
+  'DartType',
+  'Initializer',
+};
+
 /// Names of subclasses of [NamedNode] that do _not_ have `visitXReference` or
 /// `defaultXReference` methods.
 const Set<String> _classesWithoutVisitReference = const {
@@ -96,8 +110,7 @@
     '_proceduresView': null,
     '_proceduresInternal': FieldRule(name: 'procedures'),
     '_redirectingFactoriesView': null,
-    '_redirectingFactoriesInternal':
-        FieldRule(name: 'redirectingFactories'),
+    '_redirectingFactoriesInternal': FieldRule(name: 'redirectingFactories'),
     'lazyBuilder': null,
     'dirty': null,
   },
@@ -264,6 +277,7 @@
   final Class node;
   AstClassKind _kind;
   final String declarativeName;
+  final bool isInterchangeable;
 
   AstClass superclass;
   List<AstClass> interfaces = [];
@@ -273,8 +287,12 @@
   List<AstField> fields = [];
 
   AstClass(this.node,
-      {this.superclass, AstClassKind kind, this.declarativeName})
-      : _kind = kind {
+      {this.superclass,
+      AstClassKind kind,
+      this.declarativeName,
+      this.isInterchangeable})
+      : _kind = kind,
+        assert(isInterchangeable != null) {
     if (superclass != null) {
       superclass.subclasses.add(this);
     }
@@ -520,10 +538,13 @@
       _classesWithoutVisitReference.toSet();
   Map<String, Map<String, FieldRule>> fieldRuleMap = {..._fieldRuleMap};
   Map<String, FieldRule> nullFieldRules = {...?fieldRuleMap.remove(null)};
+  Set<String> interchangeableClasses = _interchangeableClasses.toSet();
   for (Class cls in astLibrary.classes) {
     declarativeClassesNames.remove(cls.name);
     classesWithoutVisitMethods.remove(cls.name);
     classesWithoutVisitReference.remove(cls.name);
+    interchangeableClasses.remove(cls.name);
+
     Map<String, FieldRule> fieldRules = {...?fieldRuleMap.remove(cls.name)};
     Set<String> renames = {};
     Class parent = cls;
@@ -575,6 +596,10 @@
     reportError('Unknown classes without visit reference methods: '
         '${classesWithoutVisitReference}');
   }
+  if (interchangeableClasses.isNotEmpty) {
+    reportError('Unknown interchangeable classes: '
+        '${interchangeableClasses}');
+  }
   if (fieldRuleMap.isNotEmpty) {
     reportError('Unknown classes with field rules: ${fieldRuleMap.keys}');
   }
@@ -615,8 +640,10 @@
 
     AstClass astClass = classMap[node];
     if (astClass == null) {
+      bool isInterchangeable = _interchangeableClasses.contains(node.name);
       if (node == classNode) {
-        astClass = new AstClass(node, kind: AstClassKind.root);
+        astClass = new AstClass(node,
+            kind: AstClassKind.root, isInterchangeable: isInterchangeable);
       } else if (classHierarchy.isSubtypeOf(node, classNode)) {
         AstClass superclass = computeAstClass(node.superclass);
         AstClassKind kind;
@@ -631,7 +658,8 @@
         astClass = new AstClass(node,
             superclass: superclass,
             kind: kind,
-            declarativeName: declarativeName);
+            declarativeName: declarativeName,
+            isInterchangeable: isInterchangeable);
         for (Supertype supertype in node.implementedTypes) {
           AstClass astSupertype = computeAstClass(supertype.classNode);
           if (astSupertype != null) {
@@ -640,11 +668,15 @@
           }
         }
       } else if (node.isEnum || _utilityClassesAsValues.contains(node.name)) {
-        astClass = new AstClass(node, kind: AstClassKind.utilityAsValue);
+        astClass = new AstClass(node,
+            kind: AstClassKind.utilityAsValue,
+            isInterchangeable: isInterchangeable);
       } else {
         AstClass superclass = computeAstClass(node.superclass);
         astClass = new AstClass(node,
-            superclass: superclass, kind: AstClassKind.utilityAsStructure);
+            superclass: superclass,
+            kind: AstClassKind.utilityAsStructure,
+            isInterchangeable: isInterchangeable);
       }
       if (astClass != null) {
         classMap[node] = astClass;
diff --git a/pkg/front_end/tool/generate_ast_coverage.dart b/pkg/front_end/tool/generate_ast_coverage.dart
new file mode 100644
index 0000000..423f2d6
--- /dev/null
+++ b/pkg/front_end/tool/generate_ast_coverage.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2021, 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.
+
+// @dart = 2.9
+
+import 'dart:io';
+
+import 'ast_model.dart';
+import 'visitor_generator.dart';
+
+Uri computeCoverageUri(Uri repoDir) {
+  return repoDir.resolve('pkg/kernel/lib/src/coverage.dart');
+}
+
+main(List<String> args) async {
+  Uri output = args.isEmpty
+      ? computeCoverageUri(Uri.base)
+      : new File(args[0]).absolute.uri;
+  String result = await generateAstCoverage(Uri.base);
+  new File.fromUri(output).writeAsStringSync(result);
+}
+
+Future<String> generateAstCoverage(Uri repoDir, [AstModel astModel]) async {
+  astModel ??= await deriveAstModel(repoDir);
+  return generateVisitor(astModel, new CoverageVisitorStrategy());
+}
+
+class CoverageVisitorStrategy extends Visitor0Strategy {
+  Map<String, Set<String>> nestedClassNames = {};
+
+  @override
+  String get returnType => 'void';
+
+  @override
+  String get visitorName => 'CoverageVisitor';
+
+  @override
+  String get visitorComment => '''
+/// Recursive visitor that collects kinds for all visited nodes.
+///
+/// This can be used to verify that tests have the intended coverage.''';
+
+  @override
+  void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
+    AstClass superAstClass = astClass.superclass;
+    while (superAstClass != null && !superAstClass.isInterchangeable) {
+      superAstClass = superAstClass.superclass;
+    }
+    String innerName = superAstClass?.name ?? 'Node';
+    (nestedClassNames[innerName] ??= {}).add(astClass.name);
+    sb.writeln('''
+    visited.add(${innerName}Kind.${astClass.name});
+    node.visitChildren(this);''');
+  }
+
+  @override
+  void handleVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
+    AstClass superAstClass = astClass.superclass;
+    while (superAstClass != null && !superAstClass.isInterchangeable) {
+      superAstClass = superAstClass.superclass;
+    }
+    if (superAstClass == astModel.constantClass) {
+      // Constants are only visited as references.
+      String innerName = superAstClass.name;
+      (nestedClassNames[innerName] ??= {}).add(astClass.name);
+      sb.writeln('''
+        visited.add(${innerName}Kind.${astClass.name});
+        node.visitChildren(this);''');
+    }
+  }
+
+  @override
+  void generateHeader(AstModel astModel, StringBuffer sb) {
+    super.generateHeader(astModel, sb);
+    sb.writeln('''
+    Set<Object> visited = {};''');
+  }
+
+  @override
+  void generateFooter(AstModel astModel, StringBuffer sb) {
+    super.generateFooter(astModel, sb);
+    nestedClassNames.forEach((String innerName, Set<String> classNames) {
+      sb.writeln('''
+
+enum ${innerName}Kind {''');
+      for (String className in classNames.toList()..sort()) {
+        sb.writeln('''
+  $className,''');
+      }
+      sb.writeln('''
+}''');
+    });
+    sb.writeln('''
+
+/// Returns the set of node kinds that were not visited by [visitor].
+Set<Object> missingNodes($visitorName visitor) {
+  Set<Object> all = {''');
+    nestedClassNames.forEach((String innerName, Set<String> classNames) {
+      sb.writeln('''
+    ...${innerName}Kind.values,''');
+    });
+    sb.writeln('''
+  };
+  all.removeAll(visitor.visited);
+  return all;
+}''');
+    nestedClassNames.forEach((String innerName, Set<String> classNames) {
+      if (innerName == 'Node') return;
+      sb.writeln('''
+/// Returns the set of [${innerName}Kind]s that were not visited by [visitor].
+Set<${innerName}Kind> missing${innerName}s($visitorName visitor) {
+  Set<${innerName}Kind> all = 
+    new Set<${innerName}Kind>.from(${innerName}Kind.values);
+  all.removeAll(visitor.visited);
+  return all;
+}''');
+    });
+  }
+}
diff --git a/pkg/front_end/tool/generate_ast_equivalence.dart b/pkg/front_end/tool/generate_ast_equivalence.dart
index 87349d6..4d2aac6 100644
--- a/pkg/front_end/tool/generate_ast_equivalence.dart
+++ b/pkg/front_end/tool/generate_ast_equivalence.dart
@@ -21,8 +21,8 @@
   new File.fromUri(output).writeAsStringSync(result);
 }
 
-Future<String> generateAstEquivalence(Uri repoDir) async {
-  AstModel astModel = await deriveAstModel(repoDir);
+Future<String> generateAstEquivalence(Uri repoDir, [AstModel astModel]) async {
+  astModel ??= await deriveAstModel(repoDir);
   return generateVisitor(astModel, new EquivalenceVisitorStrategy());
 }
 
@@ -110,7 +110,8 @@
       'check${astClass.name}_${field.name}';
 
   @override
-  void handleDefaultVisit(AstClass astClass, StringBuffer sb) {
+  void handleDefaultVisit(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
     return false;''');
   }
@@ -387,7 +388,7 @@
   }
 
   @override
-  void handleVisit(AstClass astClass, StringBuffer sb) {
+  void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
     registerAstClassEquivalence(astClass);
     sb.writeln('''
     return strategy.${classCheckName(astClass)}(
@@ -395,13 +396,15 @@
   }
 
   @override
-  void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {
+  void handleDefaultVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
     return false;''');
   }
 
   @override
-  void handleVisitReference(AstClass astClass, StringBuffer sb) {
+  void handleVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
     return false;''');
   }
@@ -504,12 +507,14 @@
     return result;
   }
 
-  /// Returns `true` if [a] and [b] are equivalent, as defined by their
-  /// corresponding canonical names. Inequivalence is _not_ registered.
+  /// Returns `true` if [a] and [b] are equivalent, either by existing 
+  /// assumption or as defined by their corresponding canonical names. 
+  /// Inequivalence is _not_ registered.
   bool $matchNamedNodes(NamedNode? a, NamedNode? b) {
     return identical(a, b) ||
         a == null ||
         b == null ||
+        checkAssumedReferences(a.reference, b.reference) ||
         new ReferenceName.fromNamedNode(a) ==
             new ReferenceName.fromNamedNode(b);
   }
@@ -527,10 +532,12 @@
     return $checkingState.$assumeReferences(a, b);
   }
 
-  /// Returns `true` if [a] and [b] are equivalent, as defined by their
-  /// corresponding canonical names. Inequivalence is _not_ registered.
+  /// Returns `true` if [a] and [b] are equivalent, either by existing
+  /// assumption or as defined by their corresponding canonical names. 
+  /// Inequivalence is _not_ registered.
   bool $matchReferences(Reference? a, Reference? b) {
     return identical(a, b) ||
+        checkAssumedReferences(a, b) ||
         ReferenceName.fromReference(a) ==
             ReferenceName.fromReference(b);
   }
diff --git a/pkg/front_end/tool/visitor_generator.dart b/pkg/front_end/tool/visitor_generator.dart
index ff3bace..a965151 100644
--- a/pkg/front_end/tool/visitor_generator.dart
+++ b/pkg/front_end/tool/visitor_generator.dart
@@ -9,7 +9,12 @@
 import 'ast_model.dart';
 
 /// Generates a visitor library into [sb] based on [astModel] and [strategy].
-String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
+///
+/// If [format] is `false`, the generated output will _not_ be formatted using
+/// the Dart formatter. Use this during development to support incomplete
+/// generation.
+String generateVisitor(AstModel astModel, VisitorStrategy strategy,
+    {bool format: true}) {
   StringBuffer sb = new StringBuffer();
   strategy.generateHeader(astModel, sb);
 
@@ -18,7 +23,7 @@
       case AstClassKind.root:
       case AstClassKind.inner:
         if (astClass.hasVisitMethod) {
-          strategy.generateDefaultVisit(astClass, sb);
+          strategy.generateDefaultVisit(astModel, astClass, sb);
         }
         for (AstClass subclass in astClass.subclasses) {
           addVisitNode(subclass);
@@ -28,7 +33,7 @@
       case AstClassKind.named:
       case AstClassKind.declarative:
         if (astClass.hasVisitMethod) {
-          strategy.generateVisit(astClass, sb);
+          strategy.generateVisit(astModel, astClass, sb);
         }
         break;
       case AstClassKind.implementation:
@@ -44,7 +49,7 @@
       case AstClassKind.root:
       case AstClassKind.inner:
         if (astClass.hasVisitReferenceMethod) {
-          strategy.generateDefaultVisitReference(astClass, sb);
+          strategy.generateDefaultVisitReference(astModel, astClass, sb);
         }
         for (AstClass subclass in astClass.subclasses) {
           addVisitReference(subclass);
@@ -54,7 +59,7 @@
       case AstClassKind.named:
       case AstClassKind.declarative:
         if (astClass.hasVisitReferenceMethod) {
-          strategy.generateVisitReference(astClass, sb);
+          strategy.generateVisitReference(astModel, astClass, sb);
         }
         break;
       case AstClassKind.implementation:
@@ -71,7 +76,9 @@
   strategy.generateFooter(astModel, sb);
 
   String result = sb.toString();
-  result = new DartFormatter().format(result);
+  if (format) {
+    result = new DartFormatter().format(result);
+  }
   return result;
 }
 
@@ -79,21 +86,27 @@
 abstract class VisitorStrategy {
   const VisitorStrategy();
 
+  /// Comment used as doc comment for the generated visitor class.
+  String get visitorComment => '';
+
   /// Generates the header of the visitor library, including preamble, imports
   /// and visitor class declaration start.
   void generateHeader(AstModel astModel, StringBuffer sb);
 
   /// Generates a `defaultX` visitor method for [astClass].
-  void generateDefaultVisit(AstClass astClass, StringBuffer sb);
+  void generateDefaultVisit(
+      AstModel astModel, AstClass astClass, StringBuffer sb);
 
   /// Generates a `visitX` visitor method for [astClass].
-  void generateVisit(AstClass astClass, StringBuffer sb);
+  void generateVisit(AstModel astModel, AstClass astClass, StringBuffer sb);
 
   /// Generates a `defaultXReference` visitor method for [astClass].
-  void generateDefaultVisitReference(AstClass astClass, StringBuffer sb);
+  void generateDefaultVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb);
 
   /// Generates a `visitXReference` visitor method for [astClass].
-  void generateVisitReference(AstClass astClass, StringBuffer sb);
+  void generateVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb);
 
   /// Generates the footer of the visitor library, including the visitor class
   /// declaration end.
@@ -119,6 +132,7 @@
     sb.writeln('''
 import 'package:kernel/ast.dart';
 
+$visitorComment
 class $visitorName$visitorTypeParameters implements Visitor<$returnType> {''');
   }
 
@@ -128,56 +142,62 @@
   }
 
   @override
-  void generateDefaultVisit(AstClass astClass, StringBuffer sb) {
+  void generateDefaultVisit(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
   @override
   ${returnType} default${astClass.name}(
       ${astClass.name} node) {''');
-    handleDefaultVisit(astClass, sb);
+    handleDefaultVisit(astModel, astClass, sb);
     sb.writeln('}');
   }
 
   /// Generates the body of a `defaultX` visitor method of [astClass].
-  void handleDefaultVisit(AstClass astClass, StringBuffer sb) {}
+  void handleDefaultVisit(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {}
 
   @override
-  void generateVisit(AstClass astClass, StringBuffer sb) {
+  void generateVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
   @override
   ${returnType} visit${astClass.name}(
       ${astClass.name} node) {''');
-    handleVisit(astClass, sb);
+    handleVisit(astModel, astClass, sb);
     sb.writeln('}');
   }
 
   /// Generates the body of a `visitX` visitor method of [astClass].
-  void handleVisit(AstClass astClass, StringBuffer sb) {}
+  void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {}
 
   @override
-  void generateDefaultVisitReference(AstClass astClass, StringBuffer sb) {
-    sb.writeln(''''
+  void generateDefaultVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
+    sb.writeln('''
   @override
   ${returnType} default${astClass.name}Reference(
-      '${astClass.name} node) {''');
-    handleDefaultVisitReference(astClass, sb);
+      ${astClass.name} node) {''');
+    handleDefaultVisitReference(astModel, astClass, sb);
     sb.writeln('}');
   }
 
   /// Generates the body of a `defaultXReference` visitor method of [astClass].
-  void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {}
+  void handleDefaultVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {}
 
   @override
-  void generateVisitReference(AstClass astClass, StringBuffer sb) {
+  void generateVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
   @override
   ${returnType} visit${astClass.name}Reference(
       ${astClass.name} node) {''');
-    handleVisitReference(astClass, sb);
+    handleVisitReference(astModel, astClass, sb);
     sb.writeln('}');
   }
 
   /// Generates the body of a `visitXReference` visitor method of [astClass].
-  void handleVisitReference(AstClass astClass, StringBuffer sb) {}
+  void handleVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {}
 }
 
 /// Strategy for creating an empty `Visitor<void>` implementation.
@@ -220,6 +240,7 @@
     sb.writeln('''
 import 'package:kernel/ast.dart';
 
+$visitorComment
 class $visitorName$visitorTypeParameters
     implements Visitor1<$returnType, $argumentType> {''');
   }
@@ -230,60 +251,66 @@
   }
 
   @override
-  void generateDefaultVisit(AstClass astClass, StringBuffer sb) {
+  void generateDefaultVisit(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
   @override
   ${returnType} default${astClass.name}(
       ${astClass.name} node, $argumentType $argumentName) {''');
-    handleDefaultVisit(astClass, sb);
+    handleDefaultVisit(astModel, astClass, sb);
     sb.writeln('''
   }''');
   }
 
   /// Generates the body of a `defaultX` visitor method of [astClass].
-  void handleDefaultVisit(AstClass astClass, StringBuffer sb) {}
+  void handleDefaultVisit(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {}
 
   @override
-  void generateVisit(AstClass astClass, StringBuffer sb) {
+  void generateVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
   @override
   ${returnType} visit${astClass.name}(
       ${astClass.name} node, $argumentType $argumentName) {''');
-    handleVisit(astClass, sb);
+    handleVisit(astModel, astClass, sb);
     sb.writeln('''
   }''');
   }
 
   /// Generates the body of a `visitX` visitor method of [astClass].
-  void handleVisit(AstClass astClass, StringBuffer sb) {}
+  void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {}
 
   @override
-  void generateDefaultVisitReference(AstClass astClass, StringBuffer sb) {
+  void generateDefaultVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
   @override
   ${returnType} default${astClass.name}Reference(
       ${astClass.name} node, $argumentType $argumentName) {''');
-    handleDefaultVisitReference(astClass, sb);
+    handleDefaultVisitReference(astModel, astClass, sb);
     sb.writeln('''
   }''');
   }
 
   /// Generates the body of a `defaultXReference` visitor method of [astClass].
-  void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {}
+  void handleDefaultVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {}
 
   @override
-  void generateVisitReference(AstClass astClass, StringBuffer sb) {
+  void generateVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {
     sb.writeln('''
   @override
   ${returnType} visit${astClass.name}Reference(
       ${astClass.name} node, $argumentType $argumentName) {''');
-    handleVisitReference(astClass, sb);
+    handleVisitReference(astModel, astClass, sb);
     sb.writeln('''
   }''');
   }
 
   /// Generates the body of a `visitXReference` visitor method of [astClass].
-  void handleVisitReference(AstClass astClass, StringBuffer sb) {}
+  void handleVisitReference(
+      AstModel astModel, AstClass astClass, StringBuffer sb) {}
 }
 
 /// Strategy for creating an empty `Visitor1<void,Null>` implementation.
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index 63cee4b..a736433 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -5328,6 +5328,7 @@
     interfaceTarget.acceptReference(v);
     name.accept(v);
     arguments.accept(v);
+    functionType?.accept(v);
   }
 
   @override
@@ -5342,6 +5343,9 @@
       arguments = v.transform(arguments);
       arguments.parent = this;
     }
+    if (functionType != null) {
+      functionType = v.visitDartType(functionType!) as FunctionType;
+    }
   }
 
   @override
@@ -5356,6 +5360,10 @@
       arguments = v.transform(arguments);
       arguments.parent = this;
     }
+    if (functionType != null) {
+      functionType =
+          v.visitDartType(functionType!, cannotRemoveSentinel) as FunctionType;
+    }
   }
 
   @override
diff --git a/pkg/kernel/lib/src/coverage.dart b/pkg/kernel/lib/src/coverage.dart
new file mode 100644
index 0000000..cfcd11d
--- /dev/null
+++ b/pkg/kernel/lib/src/coverage.dart
@@ -0,0 +1,1179 @@
+import 'package:kernel/ast.dart';
+
+/// Recursive visitor that collects kinds for all visited nodes.
+///
+/// This can be used to verify that tests have the intended coverage.
+class CoverageVisitor implements Visitor<void> {
+  Set<Object> visited = {};
+  @override
+  void defaultNode(Node node) {}
+  @override
+  void defaultTreeNode(TreeNode node) {}
+  @override
+  void visitLibrary(Library node) {
+    visited.add(NodeKind.Library);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypedef(Typedef node) {
+    visited.add(NodeKind.Typedef);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitClass(Class node) {
+    visited.add(NodeKind.Class);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitExtension(Extension node) {
+    visited.add(NodeKind.Extension);
+    node.visitChildren(this);
+  }
+
+  @override
+  void defaultMember(Member node) {}
+  @override
+  void visitField(Field node) {
+    visited.add(MemberKind.Field);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitConstructor(Constructor node) {
+    visited.add(MemberKind.Constructor);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitRedirectingFactory(RedirectingFactory node) {
+    visited.add(MemberKind.RedirectingFactory);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitProcedure(Procedure node) {
+    visited.add(MemberKind.Procedure);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLibraryDependency(LibraryDependency node) {
+    visited.add(NodeKind.LibraryDependency);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLibraryPart(LibraryPart node) {
+    visited.add(NodeKind.LibraryPart);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitCombinator(Combinator node) {
+    visited.add(NodeKind.Combinator);
+    node.visitChildren(this);
+  }
+
+  @override
+  void defaultInitializer(Initializer node) {}
+  @override
+  void visitInvalidInitializer(InvalidInitializer node) {
+    visited.add(InitializerKind.InvalidInitializer);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFieldInitializer(FieldInitializer node) {
+    visited.add(InitializerKind.FieldInitializer);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSuperInitializer(SuperInitializer node) {
+    visited.add(InitializerKind.SuperInitializer);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitRedirectingInitializer(RedirectingInitializer node) {
+    visited.add(InitializerKind.RedirectingInitializer);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLocalInitializer(LocalInitializer node) {
+    visited.add(InitializerKind.LocalInitializer);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitAssertInitializer(AssertInitializer node) {
+    visited.add(InitializerKind.AssertInitializer);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFunctionNode(FunctionNode node) {
+    visited.add(NodeKind.FunctionNode);
+    node.visitChildren(this);
+  }
+
+  @override
+  void defaultExpression(Expression node) {}
+  @override
+  void visitInvalidExpression(InvalidExpression node) {
+    visited.add(ExpressionKind.InvalidExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitVariableGet(VariableGet node) {
+    visited.add(ExpressionKind.VariableGet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitVariableSet(VariableSet node) {
+    visited.add(ExpressionKind.VariableSet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDynamicGet(DynamicGet node) {
+    visited.add(ExpressionKind.DynamicGet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceGet(InstanceGet node) {
+    visited.add(ExpressionKind.InstanceGet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFunctionTearOff(FunctionTearOff node) {
+    visited.add(ExpressionKind.FunctionTearOff);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceTearOff(InstanceTearOff node) {
+    visited.add(ExpressionKind.InstanceTearOff);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDynamicSet(DynamicSet node) {
+    visited.add(ExpressionKind.DynamicSet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceSet(InstanceSet node) {
+    visited.add(ExpressionKind.InstanceSet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSuperPropertyGet(SuperPropertyGet node) {
+    visited.add(ExpressionKind.SuperPropertyGet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSuperPropertySet(SuperPropertySet node) {
+    visited.add(ExpressionKind.SuperPropertySet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStaticGet(StaticGet node) {
+    visited.add(ExpressionKind.StaticGet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStaticTearOff(StaticTearOff node) {
+    visited.add(ExpressionKind.StaticTearOff);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStaticSet(StaticSet node) {
+    visited.add(ExpressionKind.StaticSet);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDynamicInvocation(DynamicInvocation node) {
+    visited.add(ExpressionKind.DynamicInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceInvocation(InstanceInvocation node) {
+    visited.add(ExpressionKind.InstanceInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceGetterInvocation(InstanceGetterInvocation node) {
+    visited.add(ExpressionKind.InstanceGetterInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFunctionInvocation(FunctionInvocation node) {
+    visited.add(ExpressionKind.FunctionInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLocalFunctionInvocation(LocalFunctionInvocation node) {
+    visited.add(ExpressionKind.LocalFunctionInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSuperMethodInvocation(SuperMethodInvocation node) {
+    visited.add(ExpressionKind.SuperMethodInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStaticInvocation(StaticInvocation node) {
+    visited.add(ExpressionKind.StaticInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitConstructorInvocation(ConstructorInvocation node) {
+    visited.add(ExpressionKind.ConstructorInvocation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitEqualsNull(EqualsNull node) {
+    visited.add(ExpressionKind.EqualsNull);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitEqualsCall(EqualsCall node) {
+    visited.add(ExpressionKind.EqualsCall);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstantiation(Instantiation node) {
+    visited.add(ExpressionKind.Instantiation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitNot(Not node) {
+    visited.add(ExpressionKind.Not);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLogicalExpression(LogicalExpression node) {
+    visited.add(ExpressionKind.LogicalExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitConditionalExpression(ConditionalExpression node) {
+    visited.add(ExpressionKind.ConditionalExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStringConcatenation(StringConcatenation node) {
+    visited.add(ExpressionKind.StringConcatenation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitListConcatenation(ListConcatenation node) {
+    visited.add(ExpressionKind.ListConcatenation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSetConcatenation(SetConcatenation node) {
+    visited.add(ExpressionKind.SetConcatenation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitMapConcatenation(MapConcatenation node) {
+    visited.add(ExpressionKind.MapConcatenation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceCreation(InstanceCreation node) {
+    visited.add(ExpressionKind.InstanceCreation);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFileUriExpression(FileUriExpression node) {
+    visited.add(ExpressionKind.FileUriExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitIsExpression(IsExpression node) {
+    visited.add(ExpressionKind.IsExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitAsExpression(AsExpression node) {
+    visited.add(ExpressionKind.AsExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitNullCheck(NullCheck node) {
+    visited.add(ExpressionKind.NullCheck);
+    node.visitChildren(this);
+  }
+
+  @override
+  void defaultBasicLiteral(BasicLiteral node) {}
+  @override
+  void visitStringLiteral(StringLiteral node) {
+    visited.add(ExpressionKind.StringLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitIntLiteral(IntLiteral node) {
+    visited.add(ExpressionKind.IntLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDoubleLiteral(DoubleLiteral node) {
+    visited.add(ExpressionKind.DoubleLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitBoolLiteral(BoolLiteral node) {
+    visited.add(ExpressionKind.BoolLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitNullLiteral(NullLiteral node) {
+    visited.add(ExpressionKind.NullLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSymbolLiteral(SymbolLiteral node) {
+    visited.add(ExpressionKind.SymbolLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypeLiteral(TypeLiteral node) {
+    visited.add(ExpressionKind.TypeLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitThisExpression(ThisExpression node) {
+    visited.add(ExpressionKind.ThisExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitRethrow(Rethrow node) {
+    visited.add(ExpressionKind.Rethrow);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitThrow(Throw node) {
+    visited.add(ExpressionKind.Throw);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitListLiteral(ListLiteral node) {
+    visited.add(ExpressionKind.ListLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSetLiteral(SetLiteral node) {
+    visited.add(ExpressionKind.SetLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitMapLiteral(MapLiteral node) {
+    visited.add(ExpressionKind.MapLiteral);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitAwaitExpression(AwaitExpression node) {
+    visited.add(ExpressionKind.AwaitExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    visited.add(ExpressionKind.FunctionExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitConstantExpression(ConstantExpression node) {
+    visited.add(ExpressionKind.ConstantExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLet(Let node) {
+    visited.add(ExpressionKind.Let);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitBlockExpression(BlockExpression node) {
+    visited.add(ExpressionKind.BlockExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLoadLibrary(LoadLibrary node) {
+    visited.add(ExpressionKind.LoadLibrary);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) {
+    visited.add(ExpressionKind.CheckLibraryIsLoaded);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitConstructorTearOff(ConstructorTearOff node) {
+    visited.add(ExpressionKind.ConstructorTearOff);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitRedirectingFactoryTearOff(RedirectingFactoryTearOff node) {
+    visited.add(ExpressionKind.RedirectingFactoryTearOff);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypedefTearOff(TypedefTearOff node) {
+    visited.add(ExpressionKind.TypedefTearOff);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitArguments(Arguments node) {
+    visited.add(NodeKind.Arguments);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitNamedExpression(NamedExpression node) {
+    visited.add(NodeKind.NamedExpression);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitMapLiteralEntry(MapLiteralEntry node) {
+    visited.add(NodeKind.MapLiteralEntry);
+    node.visitChildren(this);
+  }
+
+  @override
+  void defaultStatement(Statement node) {}
+  @override
+  void visitExpressionStatement(ExpressionStatement node) {
+    visited.add(StatementKind.ExpressionStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitBlock(Block node) {
+    visited.add(StatementKind.Block);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitAssertBlock(AssertBlock node) {
+    visited.add(StatementKind.AssertBlock);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitEmptyStatement(EmptyStatement node) {
+    visited.add(StatementKind.EmptyStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitAssertStatement(AssertStatement node) {
+    visited.add(StatementKind.AssertStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitLabeledStatement(LabeledStatement node) {
+    visited.add(StatementKind.LabeledStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitBreakStatement(BreakStatement node) {
+    visited.add(StatementKind.BreakStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitWhileStatement(WhileStatement node) {
+    visited.add(StatementKind.WhileStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDoStatement(DoStatement node) {
+    visited.add(StatementKind.DoStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitForStatement(ForStatement node) {
+    visited.add(StatementKind.ForStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitForInStatement(ForInStatement node) {
+    visited.add(StatementKind.ForInStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSwitchStatement(SwitchStatement node) {
+    visited.add(StatementKind.SwitchStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitContinueSwitchStatement(ContinueSwitchStatement node) {
+    visited.add(StatementKind.ContinueSwitchStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitIfStatement(IfStatement node) {
+    visited.add(StatementKind.IfStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitReturnStatement(ReturnStatement node) {
+    visited.add(StatementKind.ReturnStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTryCatch(TryCatch node) {
+    visited.add(StatementKind.TryCatch);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTryFinally(TryFinally node) {
+    visited.add(StatementKind.TryFinally);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitYieldStatement(YieldStatement node) {
+    visited.add(StatementKind.YieldStatement);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitVariableDeclaration(VariableDeclaration node) {
+    visited.add(StatementKind.VariableDeclaration);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    visited.add(StatementKind.FunctionDeclaration);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSwitchCase(SwitchCase node) {
+    visited.add(NodeKind.SwitchCase);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitCatch(Catch node) {
+    visited.add(NodeKind.Catch);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypeParameter(TypeParameter node) {
+    visited.add(NodeKind.TypeParameter);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitComponent(Component node) {
+    visited.add(NodeKind.Component);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitName(Name node) {
+    visited.add(NodeKind.Name);
+    node.visitChildren(this);
+  }
+
+  @override
+  void defaultDartType(DartType node) {}
+  @override
+  void visitInvalidType(InvalidType node) {
+    visited.add(DartTypeKind.InvalidType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDynamicType(DynamicType node) {
+    visited.add(DartTypeKind.DynamicType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitVoidType(VoidType node) {
+    visited.add(DartTypeKind.VoidType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitNeverType(NeverType node) {
+    visited.add(DartTypeKind.NeverType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitNullType(NullType node) {
+    visited.add(DartTypeKind.NullType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInterfaceType(InterfaceType node) {
+    visited.add(DartTypeKind.InterfaceType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFunctionType(FunctionType node) {
+    visited.add(DartTypeKind.FunctionType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypedefType(TypedefType node) {
+    visited.add(DartTypeKind.TypedefType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFutureOrType(FutureOrType node) {
+    visited.add(DartTypeKind.FutureOrType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitExtensionType(ExtensionType node) {
+    visited.add(DartTypeKind.ExtensionType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypeParameterType(TypeParameterType node) {
+    visited.add(DartTypeKind.TypeParameterType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitNamedType(NamedType node) {
+    visited.add(NodeKind.NamedType);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSupertype(Supertype node) {
+    visited.add(NodeKind.Supertype);
+    node.visitChildren(this);
+  }
+
+  @override
+  void defaultConstant(Constant node) {}
+  @override
+  void visitNullConstant(NullConstant node) {
+    visited.add(ConstantKind.NullConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitBoolConstant(BoolConstant node) {
+    visited.add(ConstantKind.BoolConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitIntConstant(IntConstant node) {
+    visited.add(ConstantKind.IntConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDoubleConstant(DoubleConstant node) {
+    visited.add(ConstantKind.DoubleConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStringConstant(StringConstant node) {
+    visited.add(ConstantKind.StringConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSymbolConstant(SymbolConstant node) {
+    visited.add(ConstantKind.SymbolConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitMapConstant(MapConstant node) {
+    visited.add(ConstantKind.MapConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitListConstant(ListConstant node) {
+    visited.add(ConstantKind.ListConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSetConstant(SetConstant node) {
+    visited.add(ConstantKind.SetConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceConstant(InstanceConstant node) {
+    visited.add(ConstantKind.InstanceConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstantiationConstant(InstantiationConstant node) {
+    visited.add(ConstantKind.InstantiationConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStaticTearOffConstant(StaticTearOffConstant node) {
+    visited.add(ConstantKind.StaticTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitConstructorTearOffConstant(ConstructorTearOffConstant node) {
+    visited.add(ConstantKind.ConstructorTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitRedirectingFactoryTearOffConstant(
+      RedirectingFactoryTearOffConstant node) {
+    visited.add(ConstantKind.RedirectingFactoryTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypedefTearOffConstant(TypedefTearOffConstant node) {
+    visited.add(ConstantKind.TypedefTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypeLiteralConstant(TypeLiteralConstant node) {
+    visited.add(ConstantKind.TypeLiteralConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitUnevaluatedConstant(UnevaluatedConstant node) {
+    visited.add(ConstantKind.UnevaluatedConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypedefReference(Typedef node) {}
+  @override
+  void visitClassReference(Class node) {}
+  @override
+  void visitExtensionReference(Extension node) {}
+  @override
+  void defaultMemberReference(Member node) {}
+  @override
+  void visitFieldReference(Field node) {}
+  @override
+  void visitConstructorReference(Constructor node) {}
+  @override
+  void visitRedirectingFactoryReference(RedirectingFactory node) {}
+  @override
+  void visitProcedureReference(Procedure node) {}
+  @override
+  void defaultConstantReference(Constant node) {}
+  @override
+  void visitNullConstantReference(NullConstant node) {
+    visited.add(ConstantKind.NullConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitBoolConstantReference(BoolConstant node) {
+    visited.add(ConstantKind.BoolConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitIntConstantReference(IntConstant node) {
+    visited.add(ConstantKind.IntConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitDoubleConstantReference(DoubleConstant node) {
+    visited.add(ConstantKind.DoubleConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStringConstantReference(StringConstant node) {
+    visited.add(ConstantKind.StringConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSymbolConstantReference(SymbolConstant node) {
+    visited.add(ConstantKind.SymbolConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitMapConstantReference(MapConstant node) {
+    visited.add(ConstantKind.MapConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitListConstantReference(ListConstant node) {
+    visited.add(ConstantKind.ListConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitSetConstantReference(SetConstant node) {
+    visited.add(ConstantKind.SetConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstanceConstantReference(InstanceConstant node) {
+    visited.add(ConstantKind.InstanceConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitInstantiationConstantReference(InstantiationConstant node) {
+    visited.add(ConstantKind.InstantiationConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitStaticTearOffConstantReference(StaticTearOffConstant node) {
+    visited.add(ConstantKind.StaticTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitConstructorTearOffConstantReference(
+      ConstructorTearOffConstant node) {
+    visited.add(ConstantKind.ConstructorTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitRedirectingFactoryTearOffConstantReference(
+      RedirectingFactoryTearOffConstant node) {
+    visited.add(ConstantKind.RedirectingFactoryTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypedefTearOffConstantReference(TypedefTearOffConstant node) {
+    visited.add(ConstantKind.TypedefTearOffConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitTypeLiteralConstantReference(TypeLiteralConstant node) {
+    visited.add(ConstantKind.TypeLiteralConstant);
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitUnevaluatedConstantReference(UnevaluatedConstant node) {
+    visited.add(ConstantKind.UnevaluatedConstant);
+    node.visitChildren(this);
+  }
+}
+
+enum NodeKind {
+  Arguments,
+  Catch,
+  Class,
+  Combinator,
+  Component,
+  Extension,
+  FunctionNode,
+  Library,
+  LibraryDependency,
+  LibraryPart,
+  MapLiteralEntry,
+  Name,
+  NamedExpression,
+  NamedType,
+  Supertype,
+  SwitchCase,
+  TypeParameter,
+  Typedef,
+}
+
+enum MemberKind {
+  Constructor,
+  Field,
+  Procedure,
+  RedirectingFactory,
+}
+
+enum InitializerKind {
+  AssertInitializer,
+  FieldInitializer,
+  InvalidInitializer,
+  LocalInitializer,
+  RedirectingInitializer,
+  SuperInitializer,
+}
+
+enum ExpressionKind {
+  AsExpression,
+  AwaitExpression,
+  BlockExpression,
+  BoolLiteral,
+  CheckLibraryIsLoaded,
+  ConditionalExpression,
+  ConstantExpression,
+  ConstructorInvocation,
+  ConstructorTearOff,
+  DoubleLiteral,
+  DynamicGet,
+  DynamicInvocation,
+  DynamicSet,
+  EqualsCall,
+  EqualsNull,
+  FileUriExpression,
+  FunctionExpression,
+  FunctionInvocation,
+  FunctionTearOff,
+  InstanceCreation,
+  InstanceGet,
+  InstanceGetterInvocation,
+  InstanceInvocation,
+  InstanceSet,
+  InstanceTearOff,
+  Instantiation,
+  IntLiteral,
+  InvalidExpression,
+  IsExpression,
+  Let,
+  ListConcatenation,
+  ListLiteral,
+  LoadLibrary,
+  LocalFunctionInvocation,
+  LogicalExpression,
+  MapConcatenation,
+  MapLiteral,
+  Not,
+  NullCheck,
+  NullLiteral,
+  RedirectingFactoryTearOff,
+  Rethrow,
+  SetConcatenation,
+  SetLiteral,
+  StaticGet,
+  StaticInvocation,
+  StaticSet,
+  StaticTearOff,
+  StringConcatenation,
+  StringLiteral,
+  SuperMethodInvocation,
+  SuperPropertyGet,
+  SuperPropertySet,
+  SymbolLiteral,
+  ThisExpression,
+  Throw,
+  TypeLiteral,
+  TypedefTearOff,
+  VariableGet,
+  VariableSet,
+}
+
+enum StatementKind {
+  AssertBlock,
+  AssertStatement,
+  Block,
+  BreakStatement,
+  ContinueSwitchStatement,
+  DoStatement,
+  EmptyStatement,
+  ExpressionStatement,
+  ForInStatement,
+  ForStatement,
+  FunctionDeclaration,
+  IfStatement,
+  LabeledStatement,
+  ReturnStatement,
+  SwitchStatement,
+  TryCatch,
+  TryFinally,
+  VariableDeclaration,
+  WhileStatement,
+  YieldStatement,
+}
+
+enum DartTypeKind {
+  DynamicType,
+  ExtensionType,
+  FunctionType,
+  FutureOrType,
+  InterfaceType,
+  InvalidType,
+  NeverType,
+  NullType,
+  TypeParameterType,
+  TypedefType,
+  VoidType,
+}
+
+enum ConstantKind {
+  BoolConstant,
+  ConstructorTearOffConstant,
+  DoubleConstant,
+  InstanceConstant,
+  InstantiationConstant,
+  IntConstant,
+  ListConstant,
+  MapConstant,
+  NullConstant,
+  RedirectingFactoryTearOffConstant,
+  SetConstant,
+  StaticTearOffConstant,
+  StringConstant,
+  SymbolConstant,
+  TypeLiteralConstant,
+  TypedefTearOffConstant,
+  UnevaluatedConstant,
+}
+
+/// Returns the set of node kinds that were not visited by [visitor].
+Set<Object> missingNodes(CoverageVisitor visitor) {
+  Set<Object> all = {
+    ...NodeKind.values,
+    ...MemberKind.values,
+    ...InitializerKind.values,
+    ...ExpressionKind.values,
+    ...StatementKind.values,
+    ...DartTypeKind.values,
+    ...ConstantKind.values,
+  };
+  all.removeAll(visitor.visited);
+  return all;
+}
+
+/// Returns the set of [MemberKind]s that were not visited by [visitor].
+Set<MemberKind> missingMembers(CoverageVisitor visitor) {
+  Set<MemberKind> all = new Set<MemberKind>.from(MemberKind.values);
+  all.removeAll(visitor.visited);
+  return all;
+}
+
+/// Returns the set of [InitializerKind]s that were not visited by [visitor].
+Set<InitializerKind> missingInitializers(CoverageVisitor visitor) {
+  Set<InitializerKind> all =
+      new Set<InitializerKind>.from(InitializerKind.values);
+  all.removeAll(visitor.visited);
+  return all;
+}
+
+/// Returns the set of [ExpressionKind]s that were not visited by [visitor].
+Set<ExpressionKind> missingExpressions(CoverageVisitor visitor) {
+  Set<ExpressionKind> all = new Set<ExpressionKind>.from(ExpressionKind.values);
+  all.removeAll(visitor.visited);
+  return all;
+}
+
+/// Returns the set of [StatementKind]s that were not visited by [visitor].
+Set<StatementKind> missingStatements(CoverageVisitor visitor) {
+  Set<StatementKind> all = new Set<StatementKind>.from(StatementKind.values);
+  all.removeAll(visitor.visited);
+  return all;
+}
+
+/// Returns the set of [DartTypeKind]s that were not visited by [visitor].
+Set<DartTypeKind> missingDartTypes(CoverageVisitor visitor) {
+  Set<DartTypeKind> all = new Set<DartTypeKind>.from(DartTypeKind.values);
+  all.removeAll(visitor.visited);
+  return all;
+}
+
+/// Returns the set of [ConstantKind]s that were not visited by [visitor].
+Set<ConstantKind> missingConstants(CoverageVisitor visitor) {
+  Set<ConstantKind> all = new Set<ConstantKind>.from(ConstantKind.values);
+  all.removeAll(visitor.visited);
+  return all;
+}
diff --git a/pkg/kernel/lib/src/equivalence.dart b/pkg/kernel/lib/src/equivalence.dart
index 5a1e62c..9034fe6 100644
--- a/pkg/kernel/lib/src/equivalence.dart
+++ b/pkg/kernel/lib/src/equivalence.dart
@@ -950,12 +950,14 @@
     return result;
   }
 
-  /// Returns `true` if [a] and [b] are equivalent, as defined by their
-  /// corresponding canonical names. Inequivalence is _not_ registered.
+  /// Returns `true` if [a] and [b] are equivalent, either by existing
+  /// assumption or as defined by their corresponding canonical names.
+  /// Inequivalence is _not_ registered.
   bool matchNamedNodes(NamedNode? a, NamedNode? b) {
     return identical(a, b) ||
         a == null ||
         b == null ||
+        checkAssumedReferences(a.reference, b.reference) ||
         new ReferenceName.fromNamedNode(a) ==
             new ReferenceName.fromNamedNode(b);
   }
@@ -973,10 +975,12 @@
     return _checkingState.assumeReferences(a, b);
   }
 
-  /// Returns `true` if [a] and [b] are equivalent, as defined by their
-  /// corresponding canonical names. Inequivalence is _not_ registered.
+  /// Returns `true` if [a] and [b] are equivalent, either by existing
+  /// assumption or as defined by their corresponding canonical names.
+  /// Inequivalence is _not_ registered.
   bool matchReferences(Reference? a, Reference? b) {
     return identical(a, b) ||
+        checkAssumedReferences(a, b) ||
         ReferenceName.fromReference(a) == ReferenceName.fromReference(b);
   }
 
diff --git a/pkg/kernel/lib/src/equivalence_helpers.dart b/pkg/kernel/lib/src/equivalence_helpers.dart
index a7ccb27..29f6e00 100644
--- a/pkg/kernel/lib/src/equivalence_helpers.dart
+++ b/pkg/kernel/lib/src/equivalence_helpers.dart
@@ -265,7 +265,7 @@
       return new ReferenceName.internal(ReferenceNameKind.Member, node.name,
           new ReferenceName.fromNamedNode(node.enclosingLibrary));
     } else if (node is Member) {
-      Class? enclosingClass = node.enclosingClass;
+      TreeNode? parent = node.parent;
       Reference? libraryReference = node.name.libraryName;
       String? uri;
       if (libraryReference != null) {
@@ -276,18 +276,15 @@
           uri = libraryReference.canonicalName?.name;
         }
       }
-      if (enclosingClass != null) {
-        return new ReferenceName.internal(
-            ReferenceNameKind.Member,
-            node.name.text,
-            new ReferenceName.fromNamedNode(enclosingClass),
-            uri);
+      if (parent is Class) {
+        return new ReferenceName.internal(ReferenceNameKind.Member,
+            node.name.text, new ReferenceName.fromNamedNode(parent), uri);
+      } else if (parent is Library) {
+        return new ReferenceName.internal(ReferenceNameKind.Member,
+            node.name.text, new ReferenceName.fromNamedNode(parent), uri);
       } else {
         return new ReferenceName.internal(
-            ReferenceNameKind.Member,
-            node.name.text,
-            new ReferenceName.fromNamedNode(node.enclosingLibrary),
-            uri);
+            ReferenceNameKind.Member, node.name.text, null, uri);
       }
     } else {
       throw new ArgumentError(
diff --git a/pkg/kernel/lib/src/node_creator.dart b/pkg/kernel/lib/src/node_creator.dart
new file mode 100644
index 0000000..7100c5d
--- /dev/null
+++ b/pkg/kernel/lib/src/node_creator.dart
@@ -0,0 +1,1354 @@
+// Copyright (c) 2021, 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.
+
+import '../ast.dart';
+import 'coverage.dart';
+
+/// Helper class used to generate ASTs that contain all different nodes.
+class NodeCreator {
+  final Uri _uri;
+
+  /// The parent [Component] for all created libraries, classes, extensions,
+  /// typedefs and members.
+  final Component _component = new Component();
+
+  /// These fields contain maps of requested nodes different kinds that are
+  /// still pending. The mapped values are used to track how many nodes of the
+  /// specific kind have been created. When all variants of a kind have been
+  /// created, the entry is removed from the map.
+  final Map<ExpressionKind, int> _pendingExpressions;
+  final Map<StatementKind, int> _pendingStatements;
+  final Map<DartTypeKind, int> _pendingDartTypes;
+  final Map<ConstantKind, int> _pendingConstants;
+  final Map<InitializerKind, int> _pendingInitializers;
+  final Map<MemberKind, int> _pendingMembers;
+  final Map<NodeKind, int> _pendingNodes;
+
+  /// The set of all kinds of nodes created by this node creator.
+  final Set<Object> _createdKinds = {};
+
+  /// These fields contain list of nodes needed for the creation of other nodes.
+  ///
+  /// Needed nodes are nodes that need to exist prior to the node that required
+  /// it. For instance, to create an [InterfaceType] node, a [Class] node must
+  /// exist for the [InterfaceType] to reference.
+  ///
+  /// Needed nodes are added to the context of the created nodes. For instance,
+  /// a needed [Class] is added to the [_component] and a needed
+  /// [VariableDeclaration] is added to a enclosing [Block].
+  List<Library> _neededLibraries = [];
+  List<Class> _neededClasses = [];
+  List<Extension> _neededExtensions = [];
+  List<Typedef> _neededTypedefs = [];
+  List<TypeParameter> _neededTypeParameters = [];
+  List<Constructor> _neededConstructors = [];
+  List<Procedure> _neededRedirectingFactories = [];
+  List<Procedure> _neededProcedures = [];
+  List<Field> _neededFields = [];
+  List<LibraryDependency> _neededLibraryDependencies = [];
+  List<VariableDeclaration> _neededVariableDeclarations = [];
+  List<LabeledStatement> _neededLabeledStatements = [];
+  List<FunctionDeclaration> _neededFunctionDeclarations = [];
+  List<SwitchCase> _neededSwitchCases = [];
+
+  /// Creates a [NodeCreator] requested to create nodes of the specified kinds.
+  NodeCreator({
+    Iterable<ExpressionKind> expressions: ExpressionKind.values,
+    Iterable<StatementKind> statements: StatementKind.values,
+    Iterable<DartTypeKind> dartTypes: DartTypeKind.values,
+    Iterable<ConstantKind> constants: ConstantKind.values,
+    Iterable<InitializerKind> initializers: InitializerKind.values,
+    Iterable<MemberKind> members: MemberKind.values,
+    Iterable<NodeKind> nodes: NodeKind.values,
+  })  : _pendingExpressions = new Map<ExpressionKind, int>.fromIterables(
+            expressions, new List<int>.filled(expressions.length, 0)),
+        _pendingStatements = new Map<StatementKind, int>.fromIterables(
+            statements, new List<int>.filled(statements.length, 0)),
+        _pendingDartTypes = new Map<DartTypeKind, int>.fromIterables(
+            dartTypes, new List<int>.filled(dartTypes.length, 0)),
+        _pendingConstants = new Map<ConstantKind, int>.fromIterables(
+            constants, new List<int>.filled(constants.length, 0)),
+        _pendingInitializers = new Map<InitializerKind, int>.fromIterables(
+            initializers, new List<int>.filled(initializers.length, 0)),
+        _pendingMembers = new Map<MemberKind, int>.fromIterables(
+            members, new List<int>.filled(members.length, 0)),
+        _pendingNodes = new Map<NodeKind, int>.fromIterables(
+            nodes, new List<int>.filled(nodes.length, 0)),
+        _uri = Uri.parse('test:uri') {
+    _createdKinds.addAll(_pendingExpressions.keys);
+    _createdKinds.addAll(_pendingStatements.keys);
+    _createdKinds.addAll(_pendingDartTypes.keys);
+    _createdKinds.addAll(_pendingInitializers.keys);
+    _createdKinds.addAll(_pendingMembers.keys);
+    _createdKinds.addAll(_pendingNodes.keys);
+  }
+
+  /// The kinds created by this node creator.
+  Iterable<Object> get createdKinds => _createdKinds;
+
+  /// Wraps [statement] in nodes needed in the statement context.
+  ///
+  /// For instance, if a [LabeledStatement] was needed for the creation of
+  /// [statement], [statement] is wrapped inside the labeled statement.
+  Statement _ensureContext(Statement statement) {
+    if (_neededSwitchCases.isNotEmpty) {
+      statement = SwitchStatement(NullLiteral(), [
+        ..._neededSwitchCases,
+        SwitchCase([NullLiteral()], [TreeNode.noOffset], Block([statement]))
+      ]);
+    }
+    _neededSwitchCases.clear();
+    for (LabeledStatement labeledStatement in _neededLabeledStatements) {
+      labeledStatement.body = statement;
+      statement = labeledStatement;
+    }
+    _neededLabeledStatements.clear();
+    statement = Block([
+      ..._neededVariableDeclarations,
+      ..._neededFunctionDeclarations,
+      statement
+    ]);
+    _neededFunctionDeclarations.clear();
+    _neededVariableDeclarations.clear();
+    return statement;
+  }
+
+  /// Adds [statement] to [statements] including any nodes needed in the
+  /// context.
+  void _addStatement(List<Statement> statements, Statement statement) {
+    statements.add(_ensureContext(statement));
+  }
+
+  /// Adds [expression] to [statements] including any nodes needed in the
+  /// context.
+  void _addExpression(List<Statement> statements, Expression expression) {
+    _addStatement(statements, ExpressionStatement(expression));
+  }
+
+  /// Adds [type] to [statements] including any nodes needed in the context.
+  void _addDartType(List<Statement> statements, DartType type) {
+    _addExpression(statements, TypeLiteral(type));
+  }
+
+  /// Adds [constant] to [statements] including any nodes needed in the context.
+  void _addConstant(List<Statement> statements, Constant constant) {
+    _addExpression(statements, ConstantExpression(constant));
+  }
+
+  /// Generates a list of [Statement] containing all pending in-body nodes.
+  List<Statement> _generateBodies() {
+    List<Statement> statements = [];
+    while (_pendingStatements.isNotEmpty) {
+      _addStatement(statements, _createStatement());
+    }
+    while (_pendingExpressions.isNotEmpty) {
+      _addExpression(statements, _createExpression());
+    }
+    while (_pendingDartTypes.isNotEmpty) {
+      _addDartType(statements, _createDartType());
+    }
+    while (_pendingConstants.isNotEmpty) {
+      _addConstant(statements, _createConstant());
+    }
+    for (NodeKind kind in inBodyNodeKinds) {
+      while (_pendingNodes.containsKey(kind)) {
+        Node node = _createNodeFromKind(kind);
+        switch (kind) {
+          case NodeKind.Name:
+            _addExpression(
+                statements,
+                DynamicGet(DynamicAccessKind.Dynamic, _createExpression(),
+                    node as Name));
+            break;
+          case NodeKind.Arguments:
+            _addExpression(
+                statements,
+                DynamicInvocation(DynamicAccessKind.Dynamic,
+                    _createExpression(), _createName(), node as Arguments));
+            break;
+          case NodeKind.Catch:
+            _addStatement(
+                statements, TryCatch(_createStatement(), [node as Catch]));
+            break;
+          case NodeKind.FunctionNode:
+            _addExpression(
+                statements, FunctionExpression(node as FunctionNode));
+            break;
+          case NodeKind.MapLiteralEntry:
+            _addExpression(statements, MapLiteral([node as MapLiteralEntry]));
+            break;
+          case NodeKind.NamedExpression:
+            _addExpression(
+                statements,
+                DynamicInvocation(
+                    DynamicAccessKind.Dynamic,
+                    _createExpression(),
+                    _createName(),
+                    Arguments([], named: [node as NamedExpression])));
+            break;
+          case NodeKind.NamedType:
+            _addDartType(
+                statements,
+                FunctionType([], _createDartType(), Nullability.nonNullable,
+                    namedParameters: [node as NamedType]));
+            break;
+          case NodeKind.SwitchCase:
+            _addStatement(statements,
+                SwitchStatement(_createExpression(), [node as SwitchCase]));
+            break;
+          case NodeKind.TypeParameter:
+            _addExpression(
+                statements,
+                FunctionExpression(FunctionNode(Block([]),
+                    typeParameters: [node as TypeParameter])));
+            break;
+          default:
+            throw new UnimplementedError('Unhandled in body node $kind.');
+        }
+      }
+    }
+    return statements;
+  }
+
+  /// Generates [Statement]s containing occurrences of all requested nodes.
+  List<Statement> generateBodies() {
+    List<Statement> statements = _generateBodies();
+    Set<Object> unsupportedKinds = {};
+    if (_pendingInitializers.isNotEmpty) {
+      unsupportedKinds.addAll(_pendingInitializers.keys);
+    }
+    if (_pendingMembers.isNotEmpty) {
+      unsupportedKinds.addAll(_pendingMembers.keys);
+    }
+    if (_pendingNodes.isNotEmpty) {
+      assert(
+          _pendingNodes.keys.every((kind) => !inBodyNodeKinds.contains(kind)));
+      unsupportedKinds.addAll(_pendingNodes.keys);
+    }
+    if (unsupportedKinds.isNotEmpty) {
+      throw new UnsupportedError('Cannot create these node in a body context: '
+          '${unsupportedKinds.join(', ')}');
+    }
+    return statements;
+  }
+
+  /// Generates a [Component] containing occurrences of all requested and needed
+  /// nodes.
+  Component generateComponent() {
+    Class cls = _needClass();
+    for (Statement statement in _generateBodies()) {
+      cls.addProcedure(Procedure(
+          _createName(), ProcedureKind.Method, FunctionNode(statement),
+          fileUri: _uri));
+    }
+    while (_pendingInitializers.isNotEmpty) {
+      Initializer initializer = _createInitializer();
+      cls.addConstructor(Constructor(FunctionNode(null),
+          name: _createName(), fileUri: _uri, initializers: [initializer]));
+    }
+    while (_pendingMembers.isNotEmpty) {
+      Member member = _createMember();
+      if (member is Procedure) {
+        cls.addProcedure(member);
+      } else if (member is Field) {
+        cls.addField(member);
+      } else if (member is Constructor) {
+        cls.addConstructor(member);
+      } else if (member is RedirectingFactory) {
+        cls.addRedirectingFactory(member);
+      } else {
+        throw new UnsupportedError(
+            'Unexpected member $member (${member.runtimeType})');
+      }
+    }
+    while (_pendingNodes.isNotEmpty) {
+      NodeKind kind = _pendingNodes.keys.first;
+      Node node = _createNodeFromKind(kind);
+      switch (kind) {
+        case NodeKind.Name:
+        case NodeKind.Arguments:
+        case NodeKind.Catch:
+        case NodeKind.FunctionNode:
+        case NodeKind.MapLiteralEntry:
+        case NodeKind.NamedExpression:
+        case NodeKind.NamedType:
+        case NodeKind.SwitchCase:
+        case NodeKind.TypeParameter:
+          throw new UnimplementedError('Expected in body node $kind.');
+        case NodeKind.Class:
+          _needLibrary().addClass(node as Class);
+          break;
+        case NodeKind.Combinator:
+          _needLibrary().addDependency(LibraryDependency.import(_needLibrary(),
+              combinators: [node as Combinator]));
+          break;
+        case NodeKind.Component:
+          assert(identical(node, _component),
+              "Cannot create multiple Component nodes.");
+          break;
+        case NodeKind.Extension:
+          _needLibrary().addExtension(node as Extension);
+          break;
+        case NodeKind.Library:
+          _component.libraries.add(node as Library);
+          break;
+        case NodeKind.LibraryDependency:
+          _needLibrary().addDependency(node as LibraryDependency);
+          break;
+        case NodeKind.LibraryPart:
+          _needLibrary().addPart(node as LibraryPart);
+          break;
+        case NodeKind.Supertype:
+          _needLibrary().addClass(
+              Class(name: 'foo', fileUri: _uri, supertype: node as Supertype));
+          break;
+        case NodeKind.Typedef:
+          _needLibrary().addTypedef(node as Typedef);
+          break;
+      }
+    }
+    return _component;
+  }
+
+  /// Returns a [Library] node that fits the requirements.
+  ///
+  /// If no such [Library] exists in [_neededLibraries], a new [Library] is
+  /// created and added to [_neededLibraries].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  Library _needLibrary() {
+    for (Library library in _neededLibraries) {
+      return library;
+    }
+    Library library = Library(_uri, fileUri: _uri);
+    _neededLibraries.add(library);
+    _component.libraries.add(library);
+    return library;
+  }
+
+  /// Returns a [LibraryDependency] node that fits the requirements.
+  ///
+  /// If no such [LibraryDependency] exists in [_neededLibraryDependencies], a
+  /// new [LibraryDependency] is created and added to
+  /// [_neededLibraryDependencies].
+  LibraryDependency _needLibraryDependency({bool deferred: false}) {
+    for (LibraryDependency libraryDependency in _neededLibraryDependencies) {
+      if (!deferred || libraryDependency.isDeferred) {
+        return libraryDependency;
+      }
+    }
+    LibraryDependency libraryDependency = deferred
+        ? LibraryDependency.deferredImport(_needLibrary(), 'foo')
+        : LibraryDependency.import(_needLibrary());
+    _neededLibraryDependencies.add(libraryDependency);
+    return libraryDependency;
+  }
+
+  /// Returns a [Class] node that fits the requirements.
+  ///
+  /// If no such [Class] exists in [_neededClasses], a new [Class] is
+  /// created and added to [_neededClasses].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  Class _needClass() {
+    for (Class cls in _neededClasses) {
+      return cls;
+    }
+    Class cls = Class(name: 'Foo', fileUri: _uri);
+    _neededClasses.add(cls);
+    _needLibrary().addClass(cls);
+    return cls;
+  }
+
+  /// Returns a [Extension] node that fits the requirements.
+  ///
+  /// If no such [Extension] exists in [_neededExtensions], a new [Extension] is
+  /// created and added to [_neededExtensions].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  Extension _needExtension() {
+    for (Extension extension in _neededExtensions) {
+      return extension;
+    }
+    Extension extension = Extension(name: 'foo', fileUri: _uri)
+      ..onType = DynamicType();
+    _neededExtensions.add(extension);
+    _needLibrary().addExtension(extension);
+    return extension;
+  }
+
+  /// Returns a [Typedef] node that fits the requirements.
+  ///
+  /// If no such [Typedef] exists in [_neededTypedefs], a new [Typedef] is
+  /// created and added to [_neededTypedefs].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  Typedef _needTypedef() {
+    for (Typedef typedef in _neededTypedefs) {
+      return typedef;
+    }
+    Typedef typedef = Typedef('foo', DynamicType(), fileUri: _uri);
+    _neededTypedefs.add(typedef);
+    _needLibrary().addTypedef(typedef);
+    return typedef;
+  }
+
+  /// Returns a [TypeParameter] node that fits the requirements.
+  ///
+  /// If no such [TypeParameter] exists in [_neededTypeParameters], a new
+  /// [TypeParameter] is created and added to [_neededTypeParameters].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  TypeParameter _needTypeParameter() {
+    for (TypeParameter typeParameter in _neededTypeParameters) {
+      return typeParameter;
+    }
+    TypeParameter typeParameter =
+        TypeParameter('foo', DynamicType(), DynamicType());
+    _neededTypeParameters.add(typeParameter);
+    // TODO(johnniwinther): Add the type parameter to a context; class, method
+    // or function type.
+    return typeParameter;
+  }
+
+  /// Returns a [Procedure] node that fits the requirements.
+  ///
+  /// If no such [Procedure] exists in [_neededProcedures], a new [Library] is
+  /// created and added to [_neededProcedures].
+  ///
+  /// [index] is used to create multiple distinct [Procedure] nodes even when
+  /// these have the same requirements.
+  Procedure _needProcedure({int? index, bool? isStatic}) {
+    for (Procedure procedure in _neededProcedures) {
+      if (isStatic == null || isStatic == procedure.isStatic) {
+        if (index == null || index == 0) {
+          return procedure;
+        } else {
+          index--;
+        }
+      }
+    }
+    isStatic ??= true;
+    Procedure procedure = Procedure(
+        Name('foo'), ProcedureKind.Method, FunctionNode(Block([])),
+        fileUri: _uri, isStatic: isStatic);
+    _neededProcedures.add(procedure);
+    if (isStatic) {
+      _needLibrary().addProcedure(procedure);
+    } else {
+      _needClass().addProcedure(procedure);
+    }
+    return procedure;
+  }
+
+  /// Returns a [Constructor] node that fits the requirements.
+  ///
+  /// If no such [Constructor] exists in [_neededConstructors], a new
+  /// [Constructor] is created and added to [_neededConstructors].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  Constructor _needConstructor() {
+    for (Constructor constructor in _neededConstructors) {
+      return constructor;
+    }
+    Constructor constructor =
+        Constructor(FunctionNode(null), name: Name('foo'), fileUri: _uri);
+    _needClass().addConstructor(constructor);
+    return constructor;
+  }
+
+  /// Returns a redirecting factory [Procedure] node that fits the requirements.
+  ///
+  /// If no such [Library] exists in [_neededRedirectingFactories], a new
+  /// [Procedure] is created and added to [_neededRedirectingFactories].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  Procedure _needRedirectingFactory() {
+    for (Procedure redirectingFactory in _neededRedirectingFactories) {
+      return redirectingFactory;
+    }
+    Procedure redirectingFactory = Procedure(
+        Name('foo'), ProcedureKind.Method, FunctionNode(null),
+        fileUri: _uri)
+      ..isRedirectingFactory = true;
+    _needClass().addProcedure(redirectingFactory);
+    return redirectingFactory;
+  }
+
+  /// Returns a [Field] node that fits the requirements.
+  ///
+  /// If no such [Field] exists in [_neededFields], a new [Field] is
+  /// created and added to [_neededFields].
+  ///
+  /// [index] is used to create multiple distinct [Field] nodes even when
+  /// these have the same requirements.
+  Field _needField({int? index, bool? isStatic, bool? hasSetter}) {
+    for (Field field in _neededFields) {
+      if (isStatic == null ||
+          isStatic == field.isStatic && hasSetter == null ||
+          hasSetter == field.hasSetter) {
+        if (index == null || index == 0) {
+          return field;
+        } else {
+          index--;
+        }
+      }
+    }
+    hasSetter ??= false;
+    isStatic ??= true;
+    Field field = hasSetter
+        ? new Field.immutable(Name('foo'), fileUri: _uri, isStatic: isStatic)
+        : new Field.mutable(Name('foo'), fileUri: _uri, isStatic: isStatic);
+    _neededFields.add(field);
+    if (isStatic) {
+      _needLibrary().addField(field);
+    } else {
+      _needClass().addField(field);
+    }
+    return field;
+  }
+
+  /// Returns a [VariableDeclaration] node that fits the requirements.
+  ///
+  /// If no such [VariableDeclaration] exists in [_neededVariableDeclarations],
+  /// a new [VariableDeclaration] is created and added to
+  /// [_neededVariableDeclarations].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  VariableDeclaration _needVariableDeclaration() {
+    for (VariableDeclaration variableDeclaration
+        in _neededVariableDeclarations) {
+      return variableDeclaration;
+    }
+    VariableDeclaration variableDeclaration = VariableDeclaration('foo');
+    _neededVariableDeclarations.add(variableDeclaration);
+    return variableDeclaration;
+  }
+
+  /// Returns a [LabeledStatement] node that fits the requirements.
+  ///
+  /// If no such [LabeledStatement] exists in [_neededLabeledStatements], a new
+  /// [LabeledStatement] is created and added to [_neededLabeledStatements].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  LabeledStatement _needLabeledStatement() {
+    for (LabeledStatement labeledStatement in _neededLabeledStatements) {
+      return labeledStatement;
+    }
+    LabeledStatement labeledStatement = LabeledStatement(null);
+    _neededLabeledStatements.add(labeledStatement);
+    return labeledStatement;
+  }
+
+  /// Returns a [SwitchCase] node that fits the requirements.
+  ///
+  /// If no such [SwitchCase] exists in [_neededSwitchCases], a new [SwitchCase]
+  /// is created and added to [_neededSwitchCases].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  SwitchCase _needSwitchCase() {
+    for (SwitchCase switchCase in _neededSwitchCases) {
+      return switchCase;
+    }
+    SwitchCase switchCase =
+        SwitchCase([NullLiteral()], [TreeNode.noOffset], EmptyStatement());
+    _neededSwitchCases.add(switchCase);
+    return switchCase;
+  }
+
+  /// Returns a [FunctionDeclaration] node that fits the requirements.
+  ///
+  /// If no such [FunctionDeclaration] exists in [_neededFunctionDeclarations],
+  /// a new [FunctionDeclaration] is created and added to
+  /// [_neededFunctionDeclarations].
+  // TODO(johnniwinther): Add requirements when/where needed.
+  FunctionDeclaration _needFunctionDeclaration() {
+    for (FunctionDeclaration functionDeclaration
+        in _neededFunctionDeclarations) {
+      return functionDeclaration;
+    }
+    FunctionDeclaration functionDeclaration = FunctionDeclaration(
+        VariableDeclaration('foo'), FunctionNode(Block([])));
+    _neededFunctionDeclarations.add(functionDeclaration);
+    return functionDeclaration;
+  }
+
+  /// Creates an [Expression] node.
+  ///
+  /// If there are any pending expressions, one of these is created.
+  Expression _createExpression() {
+    if (_pendingExpressions.isEmpty) {
+      return NullLiteral();
+    }
+    ExpressionKind kind = _pendingExpressions.keys.first;
+    return _createExpressionFromKind(kind);
+  }
+
+  /// Creates an [Expression] node of the specified [kind].
+  ///
+  /// If there are any pending expressions of this [kind], one of these is
+  /// created.
+  Expression _createExpressionFromKind(ExpressionKind kind) {
+    int? index = _pendingExpressions.remove(kind);
+    switch (kind) {
+      case ExpressionKind.AsExpression:
+        return AsExpression(_createExpression(), _createDartType());
+      case ExpressionKind.AwaitExpression:
+        return AwaitExpression(_createExpression());
+      case ExpressionKind.BlockExpression:
+        return BlockExpression(
+            _createStatementFromKind(StatementKind.Block) as Block,
+            _createExpression());
+      case ExpressionKind.BoolLiteral:
+        return BoolLiteral(true);
+      case ExpressionKind.CheckLibraryIsLoaded:
+        return CheckLibraryIsLoaded(_needLibraryDependency(deferred: true));
+      case ExpressionKind.ConditionalExpression:
+        return ConditionalExpression(_createExpression(), _createExpression(),
+            _createExpression(), _createDartType());
+      case ExpressionKind.ConstantExpression:
+        return ConstantExpression(_createConstant(), _createDartType());
+      case ExpressionKind.ConstructorInvocation:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => ConstructorInvocation(_needConstructor(), _createArguments(),
+              isConst: false),
+          () => ConstructorInvocation(_needConstructor(), _createArguments(),
+              isConst: true),
+        ]);
+      case ExpressionKind.ConstructorTearOff:
+        return ConstructorTearOff(_needConstructor());
+      case ExpressionKind.DoubleLiteral:
+        return DoubleLiteral(42.5);
+      case ExpressionKind.DynamicGet:
+        return DynamicGet(
+            DynamicAccessKind.Dynamic, _createExpression(), _createName());
+      case ExpressionKind.DynamicInvocation:
+        return DynamicInvocation(DynamicAccessKind.Dynamic, _createExpression(),
+            _createName(), _createArguments());
+      case ExpressionKind.DynamicSet:
+        return DynamicSet(DynamicAccessKind.Dynamic, _createExpression(),
+            _createName(), _createExpression());
+      case ExpressionKind.EqualsCall:
+        return EqualsCall(_createExpression(), _createExpression(),
+            functionType: _createFunctionType(),
+            interfaceTarget: _needProcedure());
+      case ExpressionKind.EqualsNull:
+        return EqualsNull(_createExpression());
+      case ExpressionKind.FileUriExpression:
+        return FileUriExpression(_createExpression(), _uri);
+      case ExpressionKind.FunctionExpression:
+        return FunctionExpression(_createFunctionNode());
+      case ExpressionKind.FunctionInvocation:
+        return FunctionInvocation(FunctionAccessKind.FunctionType,
+            _createExpression(), _createArguments(),
+            functionType: _createFunctionType());
+      case ExpressionKind.FunctionTearOff:
+        return FunctionTearOff(_createExpression());
+      case ExpressionKind.InstanceCreation:
+        return InstanceCreation(_needClass().reference, [], {}, [], []);
+      case ExpressionKind.InstanceGet:
+        return InstanceGet(
+            InstanceAccessKind.Instance, _createExpression(), _createName(),
+            interfaceTarget: _needField(), resultType: _createDartType());
+      case ExpressionKind.InstanceGetterInvocation:
+        return InstanceGetterInvocation(InstanceAccessKind.Instance,
+            _createExpression(), _createName(), _createArguments(),
+            interfaceTarget: _needField(), functionType: _createFunctionType());
+      case ExpressionKind.InstanceInvocation:
+        return InstanceInvocation(InstanceAccessKind.Instance,
+            _createExpression(), _createName(), _createArguments(),
+            interfaceTarget: _needProcedure(),
+            functionType: _createFunctionType())
+          ..isBoundsSafe = true;
+      case ExpressionKind.InstanceSet:
+        return InstanceSet(InstanceAccessKind.Instance, _createExpression(),
+            _createName(), _createExpression(),
+            interfaceTarget: _needField());
+      case ExpressionKind.InstanceTearOff:
+        return InstanceTearOff(
+            InstanceAccessKind.Instance, _createExpression(), _createName(),
+            interfaceTarget: _needProcedure(), resultType: _createDartType());
+      case ExpressionKind.Instantiation:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => Instantiation(_createExpression(), []),
+          () => Instantiation(_createExpression(), [_createDartType()]),
+          () => Instantiation(
+              _createExpression(), [_createDartType(), _createDartType()]),
+        ]);
+      case ExpressionKind.IntLiteral:
+        return IntLiteral(42);
+      case ExpressionKind.InvalidExpression:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => InvalidExpression(null),
+          () => InvalidExpression('foo'),
+          () => InvalidExpression('foo', _createExpression()),
+        ]);
+      case ExpressionKind.IsExpression:
+        return IsExpression(
+          _createExpression(),
+          _createDartType(),
+        );
+      case ExpressionKind.Let:
+        return Let(
+            _createStatementFromKind(StatementKind.VariableDeclaration)
+                as VariableDeclaration,
+            _createExpression());
+      case ExpressionKind.ListConcatenation:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => ListConcatenation([], typeArgument: _createDartType()),
+          () => ListConcatenation([_createExpression()],
+              typeArgument: _createDartType()),
+          () => ListConcatenation([_createExpression(), _createExpression()],
+              typeArgument: _createDartType()),
+        ]);
+      case ExpressionKind.ListLiteral:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () =>
+              ListLiteral([], typeArgument: _createDartType(), isConst: false),
+          () => ListLiteral([_createExpression()],
+              typeArgument: _createDartType(), isConst: false),
+          () => ListLiteral([_createExpression(), _createExpression()],
+              typeArgument: _createDartType(), isConst: false),
+          () => ListLiteral([_createExpression(), _createExpression()],
+              typeArgument: _createDartType(), isConst: true),
+        ]);
+      case ExpressionKind.LoadLibrary:
+        return LoadLibrary(_needLibraryDependency(deferred: true));
+      case ExpressionKind.LocalFunctionInvocation:
+        return LocalFunctionInvocation(
+            _needFunctionDeclaration().variable, _createArguments(),
+            functionType: _createFunctionType());
+      case ExpressionKind.LogicalExpression:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => LogicalExpression(_createExpression(),
+              LogicalExpressionOperator.AND, _createExpression()),
+          () => LogicalExpression(_createExpression(),
+              LogicalExpressionOperator.OR, _createExpression()),
+        ]);
+      case ExpressionKind.MapConcatenation:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => MapConcatenation([],
+              keyType: _createDartType(), valueType: _createDartType()),
+          () => MapConcatenation([_createExpression()],
+              keyType: _createDartType(), valueType: _createDartType()),
+          () => MapConcatenation([_createExpression(), _createExpression()],
+              keyType: _createDartType(), valueType: _createDartType()),
+        ]);
+      case ExpressionKind.MapLiteral:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => MapLiteral([],
+              keyType: _createDartType(),
+              valueType: _createDartType(),
+              isConst: false),
+          () => MapLiteral([_createMapLiteralEntry()],
+              keyType: _createDartType(),
+              valueType: _createDartType(),
+              isConst: false),
+          () => MapLiteral([
+                _createMapLiteralEntry(),
+                _createMapLiteralEntry(),
+              ],
+                  keyType: _createDartType(),
+                  valueType: _createDartType(),
+                  isConst: false),
+          () => MapLiteral([
+                _createMapLiteralEntry(),
+                _createMapLiteralEntry(),
+              ],
+                  keyType: _createDartType(),
+                  valueType: _createDartType(),
+                  isConst: true),
+        ]);
+      case ExpressionKind.Not:
+        return Not(_createExpression());
+      case ExpressionKind.NullCheck:
+        return NullCheck(_createExpression());
+      case ExpressionKind.NullLiteral:
+        return NullLiteral();
+      case ExpressionKind.RedirectingFactoryTearOff:
+        return RedirectingFactoryTearOff(_needRedirectingFactory());
+      case ExpressionKind.Rethrow:
+        return Rethrow();
+      case ExpressionKind.SetConcatenation:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => SetConcatenation([], typeArgument: _createDartType()),
+          () => SetConcatenation([_createExpression()],
+              typeArgument: _createDartType()),
+          () => SetConcatenation([_createExpression(), _createExpression()],
+              typeArgument: _createDartType()),
+        ]);
+      case ExpressionKind.SetLiteral:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => SetLiteral([], typeArgument: _createDartType(), isConst: false),
+          () => SetLiteral([_createExpression()],
+              typeArgument: _createDartType(), isConst: false),
+          () => SetLiteral([_createExpression(), _createExpression()],
+              typeArgument: _createDartType(), isConst: false),
+          () => SetLiteral([_createExpression(), _createExpression()],
+              typeArgument: _createDartType(), isConst: true),
+        ]);
+      case ExpressionKind.StaticGet:
+        return StaticGet(_needField());
+      case ExpressionKind.StaticInvocation:
+        return StaticInvocation(_needProcedure(), _createArguments());
+      case ExpressionKind.StaticSet:
+        return StaticSet(_needField(), _createExpression());
+      case ExpressionKind.StaticTearOff:
+        return StaticTearOff(_needProcedure());
+      case ExpressionKind.StringConcatenation:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => StringConcatenation([]),
+          () => StringConcatenation([_createExpression()]),
+          () => StringConcatenation([_createExpression(), _createExpression()]),
+        ]);
+      case ExpressionKind.StringLiteral:
+        return StringLiteral('foo');
+      case ExpressionKind.SuperMethodInvocation:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => SuperMethodInvocation(_createName(), _createArguments()),
+          () => SuperMethodInvocation(
+              _createName(), _createArguments(), _needProcedure()),
+        ]);
+      case ExpressionKind.SuperPropertyGet:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => SuperPropertyGet(_createName()),
+          () => SuperPropertyGet(_createName(), _needField()),
+        ]);
+      case ExpressionKind.SuperPropertySet:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => SuperPropertySet(_createName(), _createExpression(), null),
+          () => SuperPropertySet(
+              _createName(), _createExpression(), _needField()),
+        ]);
+      case ExpressionKind.SymbolLiteral:
+        return SymbolLiteral('foo');
+      case ExpressionKind.ThisExpression:
+        return ThisExpression();
+      case ExpressionKind.Throw:
+        return Throw(_createExpression());
+      case ExpressionKind.TypeLiteral:
+        return TypeLiteral(_createDartType());
+      case ExpressionKind.TypedefTearOff:
+        // TODO(johnniwinther): Add non-trivial cases.
+        return TypedefTearOff([], _createExpression(), []);
+      case ExpressionKind.VariableGet:
+        return _createOneOf(_pendingExpressions, kind, index, [
+          () => VariableGet(_needVariableDeclaration()),
+          () => VariableGet(_needVariableDeclaration(), _createDartType()),
+        ]);
+      case ExpressionKind.VariableSet:
+        return VariableSet(_needVariableDeclaration(), _createExpression());
+    }
+  }
+
+  /// Creates a [Statement] node.
+  ///
+  /// If there are any pending statements, one of these is created.
+  Statement _createStatement() {
+    if (_pendingStatements.isEmpty) {
+      return EmptyStatement();
+    }
+    StatementKind kind = _pendingStatements.keys.first;
+    return _createStatementFromKind(kind);
+  }
+
+  /// Creates a [Statement] of the specified [kind].
+  ///
+  /// If there are any pending statements of this [kind], one of these is
+  /// created.
+  Statement _createStatementFromKind(StatementKind kind) {
+    int? index = _pendingStatements.remove(kind);
+    switch (kind) {
+      case StatementKind.AssertBlock:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => AssertBlock([]),
+          () => AssertBlock([_createStatement()]),
+          () => AssertBlock([_createStatement(), _createStatement()]),
+        ]);
+      case StatementKind.AssertStatement:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => AssertStatement(_createExpression(),
+              conditionStartOffset: TreeNode.noOffset,
+              conditionEndOffset: TreeNode.noOffset),
+          () => AssertStatement(_createExpression(),
+              message: _createExpression(),
+              conditionStartOffset: TreeNode.noOffset,
+              conditionEndOffset: TreeNode.noOffset),
+        ]);
+      case StatementKind.Block:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => Block([]),
+          () => Block([_createStatement()]),
+          () => Block([_createStatement(), _createStatement()]),
+        ]);
+      case StatementKind.BreakStatement:
+        return BreakStatement(_needLabeledStatement());
+      case StatementKind.ContinueSwitchStatement:
+        return ContinueSwitchStatement(_needSwitchCase());
+      case StatementKind.DoStatement:
+        return DoStatement(_createStatement(), _createExpression());
+      case StatementKind.EmptyStatement:
+        return EmptyStatement();
+      case StatementKind.ExpressionStatement:
+        return ExpressionStatement(_createExpression());
+      case StatementKind.ForInStatement:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => ForInStatement(_createVariableDeclaration(),
+              _createExpression(), _createStatement(),
+              isAsync: false),
+          () => ForInStatement(_createVariableDeclaration(),
+              _createExpression(), _createStatement(),
+              isAsync: true),
+        ]);
+      case StatementKind.ForStatement:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => ForStatement([], null, [], _createStatement()),
+          () => ForStatement([_createVariableDeclaration()],
+              _createExpression(), [_createExpression()], _createStatement()),
+          () => ForStatement(
+              [_createVariableDeclaration(), _createVariableDeclaration()],
+              _createExpression(),
+              [_createExpression(), _createExpression()],
+              _createStatement()),
+        ]);
+      case StatementKind.FunctionDeclaration:
+        return FunctionDeclaration(
+            VariableDeclaration(null), _createFunctionNode());
+      case StatementKind.IfStatement:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => IfStatement(_createExpression(), _createStatement(), null),
+          () => IfStatement(
+              _createExpression(), _createStatement(), _createStatement()),
+        ]);
+      case StatementKind.LabeledStatement:
+        return LabeledStatement(_createStatement());
+      case StatementKind.ReturnStatement:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => ReturnStatement(),
+          () => ReturnStatement(_createExpression()),
+        ]);
+      case StatementKind.SwitchStatement:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => SwitchStatement(_createExpression(), []),
+          () => SwitchStatement(_createExpression(), [_createSwitchCase()]),
+          () => SwitchStatement(
+              _createExpression(), [_createSwitchCase(), _createSwitchCase()]),
+        ]);
+      case StatementKind.TryCatch:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => TryCatch(_createStatement(), []),
+          () => TryCatch(_createStatement(), [_createCatch()]),
+          () => TryCatch(_createStatement(), [_createCatch(), _createCatch()]),
+        ]);
+      case StatementKind.TryFinally:
+        return TryFinally(_createStatement(), _createStatement());
+      case StatementKind.VariableDeclaration:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => VariableDeclaration('foo'),
+          () => VariableDeclaration('foo', initializer: _createExpression()),
+          () => VariableDeclaration('foo', type: _createDartType()),
+        ]);
+      case StatementKind.WhileStatement:
+        return WhileStatement(_createExpression(), _createStatement());
+      case StatementKind.YieldStatement:
+        return _createOneOf(_pendingStatements, kind, index, [
+          () => YieldStatement(_createExpression(), isYieldStar: false),
+          () => YieldStatement(_createExpression(), isYieldStar: true),
+        ]);
+    }
+  }
+
+  /// Creates a [VariableDeclaration] node.
+  ///
+  /// If there are any pending [VariableDeclaration] nodes, one of these is
+  /// created.
+  VariableDeclaration _createVariableDeclaration() {
+    return _createStatementFromKind(StatementKind.VariableDeclaration)
+        as VariableDeclaration;
+  }
+
+  /// Creates a [DartType] node.
+  ///
+  /// If there are any pending types, one of these is created.
+  DartType _createDartType() {
+    if (_pendingDartTypes.isEmpty) {
+      return VoidType();
+    }
+    DartTypeKind kind = _pendingDartTypes.keys.first;
+    return _createDartTypeFromKind(kind);
+  }
+
+  /// Creates a [DartType] node of the specified [kind].
+  ///
+  /// If there are any pending types of this [kind], one of these is created.
+  DartType _createDartTypeFromKind(DartTypeKind kind) {
+    int? index = _pendingDartTypes.remove(kind);
+    switch (kind) {
+      case DartTypeKind.DynamicType:
+        return DynamicType();
+      case DartTypeKind.ExtensionType:
+        return ExtensionType(_needExtension(), Nullability.nonNullable);
+      case DartTypeKind.FunctionType:
+        return _createOneOf(_pendingDartTypes, kind, index, [
+          // TODO(johnniwinther): Create non-trivial cases.
+          () => FunctionType([], _createDartType(), Nullability.nonNullable),
+        ]);
+      case DartTypeKind.FutureOrType:
+        return FutureOrType(_createDartType(), Nullability.nonNullable);
+      case DartTypeKind.InterfaceType:
+        return _createOneOf(_pendingDartTypes, kind, index, [
+          // TODO(johnniwinther): Create non-trivial cases.
+          () => InterfaceType(_needClass(), Nullability.nonNullable, []),
+        ]);
+      case DartTypeKind.InvalidType:
+        return InvalidType();
+      case DartTypeKind.NeverType:
+        return NeverType.nonNullable();
+      case DartTypeKind.NullType:
+        return NullType();
+      case DartTypeKind.TypeParameterType:
+        return _createOneOf(_pendingDartTypes, kind, index, [
+          () =>
+              TypeParameterType(_needTypeParameter(), Nullability.nonNullable),
+          () => TypeParameterType(
+              _needTypeParameter(), Nullability.nonNullable, _createDartType()),
+        ]);
+      case DartTypeKind.TypedefType:
+        return _createOneOf(_pendingDartTypes, kind, index, [
+          // TODO(johnniwinther): Create non-trivial cases.
+          () => TypedefType(_needTypedef(), Nullability.nonNullable, []),
+        ]);
+      case DartTypeKind.VoidType:
+        return VoidType();
+    }
+  }
+
+  /// Creates a [FunctionType] node.
+  ///
+  /// If there are any pending [FunctionType] nodes, one of these is created.
+  FunctionType _createFunctionType() {
+    return _createDartTypeFromKind(DartTypeKind.FunctionType) as FunctionType;
+  }
+
+  /// Creates a [Constant] node.
+  ///
+  /// If there are any pending constants, one of these is created.
+  Constant _createConstant() {
+    if (_pendingConstants.isEmpty) {
+      return NullConstant();
+    }
+    ConstantKind kind = _pendingConstants.keys.first;
+    return _createConstantFromKind(kind);
+  }
+
+  /// Creates a [Constant] node of the specified [kind].
+  ///
+  /// If there are any pending constants of this [kind], one of these is
+  /// created.
+  Constant _createConstantFromKind(ConstantKind kind) {
+    int? index = _pendingConstants.remove(kind);
+    switch (kind) {
+      case ConstantKind.BoolConstant:
+        return BoolConstant(true);
+      case ConstantKind.ConstructorTearOffConstant:
+        return ConstructorTearOffConstant(_needConstructor());
+      case ConstantKind.DoubleConstant:
+        return DoubleConstant(42.5);
+      case ConstantKind.InstanceConstant:
+        return _createOneOf(_pendingConstants, kind, index, [
+          () => InstanceConstant(_needClass().reference, [], {}),
+          () => InstanceConstant(_needClass().reference, [
+                _createDartType()
+              ], {
+                _needField(isStatic: false, hasSetter: false).getterReference:
+                    _createConstant()
+              }),
+          () => InstanceConstant(_needClass().reference, [
+                _createDartType()
+              ], {
+                _needField(index: 0, isStatic: false, hasSetter: false)
+                    .getterReference: _createConstant(),
+                _needField(index: 1, isStatic: false, hasSetter: false)
+                    .getterReference: _createConstant()
+              }),
+        ]);
+      case ConstantKind.InstantiationConstant:
+        return InstantiationConstant(_createConstant(), [_createDartType()]);
+      case ConstantKind.IntConstant:
+        return IntConstant(42);
+      case ConstantKind.ListConstant:
+        return _createOneOf(_pendingConstants, kind, index, [
+          () => ListConstant(_createDartType(), []),
+          () => ListConstant(_createDartType(), [_createConstant()]),
+          () => ListConstant(
+              _createDartType(), [_createConstant(), _createConstant()]),
+        ]);
+      case ConstantKind.MapConstant:
+        return _createOneOf(_pendingConstants, kind, index, [
+          () => MapConstant(_createDartType(), _createDartType(), []),
+          () => MapConstant(_createDartType(), _createDartType(),
+              [ConstantMapEntry(_createConstant(), _createConstant())]),
+          () => MapConstant(_createDartType(), _createDartType(), [
+                ConstantMapEntry(_createConstant(), _createConstant()),
+                ConstantMapEntry(_createConstant(), _createConstant())
+              ]),
+        ]);
+      case ConstantKind.NullConstant:
+        return NullConstant();
+      case ConstantKind.RedirectingFactoryTearOffConstant:
+        return RedirectingFactoryTearOffConstant(_needRedirectingFactory());
+      case ConstantKind.SetConstant:
+        return _createOneOf(_pendingConstants, kind, index, [
+          () => SetConstant(_createDartType(), []),
+          () => SetConstant(_createDartType(), [_createConstant()]),
+          () => SetConstant(
+              _createDartType(), [_createConstant(), _createConstant()]),
+        ]);
+      case ConstantKind.StaticTearOffConstant:
+        return StaticTearOffConstant(_needProcedure());
+      case ConstantKind.StringConstant:
+        return StringConstant('foo');
+      case ConstantKind.SymbolConstant:
+        return _createOneOf(_pendingConstants, kind, index, [
+          () => SymbolConstant('foo', null),
+          () => SymbolConstant('_foo', _needLibrary().reference),
+        ]);
+      case ConstantKind.TypeLiteralConstant:
+        return TypeLiteralConstant(_createDartType());
+      case ConstantKind.TypedefTearOffConstant:
+        // TODO(johnniwinther): Add non-trivial cases.
+        return TypedefTearOffConstant(
+            [],
+            _createConstantFromKind(ConstantKind.ConstructorTearOffConstant)
+                as TearOffConstant,
+            []);
+      case ConstantKind.UnevaluatedConstant:
+        return UnevaluatedConstant(_createExpression());
+    }
+  }
+
+  /// Creates an [Initializer] node.
+  ///
+  /// If there are any pending initializers, one of these is created.
+  Initializer _createInitializer() {
+    if (_pendingInitializers.isEmpty) {
+      return InvalidInitializer();
+    }
+    InitializerKind kind = _pendingInitializers.keys.first;
+    return _createInitializerFromKind(kind);
+  }
+
+  /// Creates an [Initializer] node of the specified [kind].
+  ///
+  /// If there are any pending initializers of this [kind], one of these is
+  /// created.
+  Initializer _createInitializerFromKind(InitializerKind kind) {
+    // ignore: unused_local_variable
+    int? index = _pendingInitializers.remove(kind);
+    switch (kind) {
+      case InitializerKind.AssertInitializer:
+        return AssertInitializer(
+            _createStatementFromKind(StatementKind.AssertStatement)
+                as AssertStatement);
+      case InitializerKind.FieldInitializer:
+        return FieldInitializer(
+            _needField(isStatic: false), _createExpression());
+      case InitializerKind.InvalidInitializer:
+        return InvalidInitializer();
+      case InitializerKind.LocalInitializer:
+        return LocalInitializer(_createVariableDeclaration());
+      case InitializerKind.RedirectingInitializer:
+        return RedirectingInitializer(_needConstructor(), _createArguments());
+      case InitializerKind.SuperInitializer:
+        return SuperInitializer(_needConstructor(), _createArguments());
+    }
+  }
+
+  /// Creates a [Member] node.
+  ///
+  /// If there are any pending members, one of these is created.
+  Member _createMember() {
+    if (_pendingMembers.isEmpty) {
+      return Field.immutable(_createName(), fileUri: _uri);
+    }
+    MemberKind kind = _pendingMembers.keys.first;
+    return _createMemberKind(kind);
+  }
+
+  /// Creates a [Member] node of the specified [kind].
+  ///
+  /// If there are any pending members of this [kind], one of these is created.
+  Member _createMemberKind(MemberKind kind) {
+    int? index = _pendingMembers.remove(kind);
+    switch (kind) {
+      case MemberKind.Constructor:
+        return Constructor(_createFunctionNode(),
+            name: _createName(), fileUri: _uri);
+      case MemberKind.Field:
+        return _createOneOf(_pendingMembers, kind, index, [
+          () => Field.mutable(_createName(), fileUri: _uri),
+          () => Field.immutable(_createName(), fileUri: _uri),
+        ]);
+      case MemberKind.Procedure:
+        return _createOneOf(_pendingMembers, kind, index, [
+          () => Procedure(
+              _createName(), ProcedureKind.Method, _createFunctionNode(),
+              fileUri: _uri),
+          () => Procedure(
+              _createName(), ProcedureKind.Operator, _createFunctionNode(),
+              fileUri: _uri),
+          () => Procedure(
+              _createName(), ProcedureKind.Getter, _createFunctionNode(),
+              fileUri: _uri),
+          () => Procedure(
+              _createName(), ProcedureKind.Setter, _createFunctionNode(),
+              fileUri: _uri),
+        ]);
+      case MemberKind.RedirectingFactory:
+        return RedirectingFactory(null,
+            name: _createName(),
+            function: _createFunctionNode(),
+            fileUri: _uri);
+    }
+  }
+
+  /// Creates an [Arguments] node.
+  ///
+  /// If there are any pending [Arguments] nodes, one of these is created.
+  Arguments _createArguments() {
+    return _createNodeFromKind(NodeKind.Arguments) as Arguments;
+  }
+
+  /// Creates a [Name] node.
+  ///
+  /// If there are any pending [Name] nodes, one of these is created.
+  Name _createName() {
+    return _createNodeFromKind(NodeKind.Name) as Name;
+  }
+
+  /// Creates a [FunctionNode] node.
+  ///
+  /// If there are any pending [FunctionNode] nodes, one of these is created.
+  FunctionNode _createFunctionNode() {
+    return _createNodeFromKind(NodeKind.FunctionNode) as FunctionNode;
+  }
+
+  /// Creates a [MapLiteralEntry] node.
+  ///
+  /// If there are any pending [MapLiteralEntry] nodes, one of these is created.
+  MapLiteralEntry _createMapLiteralEntry() {
+    return _createNodeFromKind(NodeKind.MapLiteralEntry) as MapLiteralEntry;
+  }
+
+  /// Creates a [SwitchCase] node.
+  ///
+  /// If there are any pending [SwitchCase] nodes, one of these is created.
+  SwitchCase _createSwitchCase() {
+    return _createNodeFromKind(NodeKind.SwitchCase) as SwitchCase;
+  }
+
+  /// Creates a [Catch] node.
+  ///
+  /// If there are any pending [Catch] nodes, one of these is created.
+  Catch _createCatch() {
+    return _createNodeFromKind(NodeKind.Catch) as Catch;
+  }
+
+  /// Creates a [Node] of the specified [kind].
+  ///
+  /// If there are any pending nodes of this [kind], one of these is created.
+  Node _createNodeFromKind(NodeKind kind) {
+    int? index = _pendingNodes.remove(kind);
+    switch (kind) {
+      case NodeKind.Arguments:
+        return _createOneOf(_pendingNodes, kind, index, [
+          // TODO(johnniwinther): Add non-trivial cases.
+          () => Arguments([]),
+        ]);
+      case NodeKind.Catch:
+        // TODO(johnniwinther): Add non-trivial cases.
+        return Catch(null, _createStatement());
+      case NodeKind.Class:
+        return Class(name: 'foo', fileUri: _uri);
+      case NodeKind.Combinator:
+        return _createOneOf(_pendingNodes, kind, index, [
+          () => Combinator.show([]),
+          () => Combinator.show(['foo']),
+          () => Combinator.show(['foo', 'bar']),
+          () => Combinator.hide([]),
+          () => Combinator.hide(['foo']),
+          () => Combinator.hide(['foo', 'bar']),
+        ]);
+      case NodeKind.Component:
+        return _component;
+      case NodeKind.Extension:
+        // TODO(johnniwinther): Add non-trivial cases.
+        return Extension(name: 'foo', fileUri: _uri)
+          ..onType = _createDartType();
+      case NodeKind.FunctionNode:
+        // TODO(johnniwinther): Add non-trivial cases.
+        return FunctionNode(_createStatement());
+      case NodeKind.Library:
+        return Library(_uri, fileUri: _uri);
+      case NodeKind.LibraryDependency:
+        return _createOneOf(_pendingNodes, kind, index, [
+          // TODO(johnniwinther): Add more cases.
+          () => LibraryDependency.import(_needLibrary()),
+          () => LibraryDependency.import(_needLibrary(), name: 'foo'),
+          () => LibraryDependency.export(_needLibrary()),
+        ]);
+      case NodeKind.LibraryPart:
+        // TODO(johnniwinther): Add non-trivial cases.
+        // TODO(johnniwinther): Do we need to use a valid part uri?
+        return LibraryPart([], 'foo');
+      case NodeKind.MapLiteralEntry:
+        return MapLiteralEntry(_createExpression(), _createExpression());
+      case NodeKind.Name:
+        return _createOneOf(_pendingNodes, kind, index, [
+          () => Name('foo'),
+          () => Name('_foo', _needLibrary()),
+        ]);
+      case NodeKind.NamedExpression:
+        return NamedExpression('foo', _createExpression());
+      case NodeKind.NamedType:
+        return NamedType('foo', _createDartType());
+      case NodeKind.Supertype:
+        return _createOneOf(_pendingNodes, kind, index, [
+          () => Supertype(_needClass(), []),
+          () => Supertype(_needClass(), [_createDartType()]),
+          () => Supertype(_needClass(), [_createDartType(), _createDartType()]),
+        ]);
+      case NodeKind.SwitchCase:
+        // TODO(johnniwinther): Add non-trivial cases.
+        return SwitchCase(
+            [NullLiteral()], [TreeNode.noOffset], _createStatement());
+      case NodeKind.TypeParameter:
+        return TypeParameter('foo', _createDartType(), _createDartType());
+      case NodeKind.Typedef:
+        return Typedef('foo', _createDartType(), fileUri: _uri);
+    }
+  }
+
+  /// Helper that creates a node of type [V] using the list of [creators].
+  ///
+  /// The [index] indicates how many nodes of the [kind] that have currently
+  /// been created. If there are more [creators] left after having created the
+  /// [index]th node, [pending] is updated with the next pending index. If all
+  /// pending nodes of the given [kind] have been created, [index] is `null` and
+  /// the first [creators] function is used.
+  V _createOneOf<K, V>(
+      Map<K, int> pending, K kind, int? index, List<V Function()> creators) {
+    if (index == null) {
+      // All pending nodes have been created so we just create the first.
+      return creators[0]();
+    }
+    if (index + 1 < creators.length) {
+      pending[kind] = index + 1;
+    }
+    return creators[index]();
+  }
+}
+
+/// [NodeKind]s for nodes that can occur inside member bodies.
+const Set<NodeKind> inBodyNodeKinds = {
+  NodeKind.Arguments,
+  NodeKind.Catch,
+  NodeKind.FunctionNode,
+  NodeKind.MapLiteralEntry,
+  NodeKind.Name,
+  NodeKind.NamedExpression,
+  NodeKind.NamedType,
+  NodeKind.SwitchCase,
+  NodeKind.TypeParameter,
+};
diff --git a/pkg/kernel/lib/src/text_util.dart b/pkg/kernel/lib/src/text_util.dart
index 5871066..ac95aac 100644
--- a/pkg/kernel/lib/src/text_util.dart
+++ b/pkg/kernel/lib/src/text_util.dart
@@ -33,10 +33,9 @@
 
 String qualifiedClassNameToString(Class node,
     {bool includeLibraryName: false}) {
-  if (includeLibraryName) {
-    return libraryNameToString(node.enclosingLibrary) +
-        '::' +
-        classNameToString(node);
+  TreeNode? parent = node.parent;
+  if (parent is Library && includeLibraryName) {
+    return libraryNameToString(parent) + '::' + classNameToString(node);
   } else {
     return classNameToString(node);
   }
@@ -90,10 +89,9 @@
 
 String qualifiedExtensionNameToString(Extension node,
     {bool includeLibraryName: false}) {
-  if (includeLibraryName) {
-    return libraryNameToString(node.enclosingLibrary) +
-        '::' +
-        extensionNameToString(node);
+  TreeNode? parent = node.parent;
+  if (parent is Library && includeLibraryName) {
+    return libraryNameToString(parent) + '::' + extensionNameToString(node);
   } else {
     return extensionNameToString(node);
   }
@@ -127,10 +125,9 @@
 
 String qualifiedTypedefNameToString(Typedef node,
     {bool includeLibraryName: false}) {
-  if (includeLibraryName) {
-    return libraryNameToString(node.enclosingLibrary) +
-        '::' +
-        typedefNameToString(node);
+  TreeNode? parent = node.parent;
+  if (parent is Library && includeLibraryName) {
+    return libraryNameToString(parent) + '::' + typedefNameToString(node);
   } else {
     return typedefNameToString(node);
   }
@@ -164,15 +161,14 @@
 
 String qualifiedMemberNameToString(Member node,
     {bool includeLibraryName: false}) {
-  if (node.enclosingClass != null) {
-    return qualifiedClassNameToString(node.enclosingClass!,
+  TreeNode? parent = node.parent;
+  if (parent is Class) {
+    return qualifiedClassNameToString(parent,
             includeLibraryName: includeLibraryName) +
         '.' +
         memberNameToString(node);
-  } else if (includeLibraryName) {
-    return libraryNameToString(node.enclosingLibrary) +
-        '::' +
-        memberNameToString(node);
+  } else if (parent is Library && includeLibraryName) {
+    return libraryNameToString(parent) + '::' + memberNameToString(node);
   } else {
     return memberNameToString(node);
   }
diff --git a/pkg/kernel/test/clone_test.dart b/pkg/kernel/test/clone_test.dart
new file mode 100644
index 0000000..194e4b5
--- /dev/null
+++ b/pkg/kernel/test/clone_test.dart
@@ -0,0 +1,170 @@
+// Copyright (c) 2021, 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.
+
+import 'package:expect/expect.dart';
+import 'package:kernel/ast.dart';
+import 'package:kernel/clone.dart';
+import 'package:kernel/src/coverage.dart';
+import 'package:kernel/src/equivalence.dart';
+import 'package:kernel/src/node_creator.dart';
+
+main() {
+  testBodyCloning();
+  testMemberCloning();
+}
+
+void testBodyCloning() {
+  // TODO(johnniwinther): Add a test for cloning in context.
+  NodeCreator creator =
+      new NodeCreator(initializers: [], members: [], nodes: inBodyNodeKinds);
+  List<TreeNode> nodes = creator.generateBodies();
+
+  CoverageVisitor coverageVisitor = new CoverageVisitor();
+  for (TreeNode node in nodes) {
+    node.accept(coverageVisitor);
+    CloneVisitorNotMembers cloner = new CloneVisitorNotMembers();
+    TreeNode clone = cloner.clone(node);
+    EquivalenceResult result = checkEquivalence(node, clone);
+    if (!result.isEquivalent) {
+      print(result);
+    }
+    Expect.isTrue(result.isEquivalent, "$node");
+  }
+  Expect.isEmpty(
+      creator.createdKinds.toSet()..removeAll(coverageVisitor.visited),
+      'Nodes not covered in testing.');
+}
+
+void testMemberCloning() {
+  NodeCreator creator = new NodeCreator(nodes: inBodyNodeKinds);
+  Component component = creator.generateComponent();
+
+  CoverageVisitor coverageVisitor = new CoverageVisitor();
+
+  void testMembers<M extends Member>(
+      Iterable<M> members,
+      M Function(CloneVisitorWithMembers, M) cloneFunction,
+      String Function(M) toStringFunction) {
+    for (M member in members) {
+      member.accept(coverageVisitor);
+      CloneVisitorWithMembers cloner = new CloneVisitorWithMembers();
+      M clone = cloneFunction(cloner, member);
+      EquivalenceResult result = checkEquivalence(member, clone,
+          strategy: const MemberEquivalenceStrategy());
+      if (!result.isEquivalent) {
+        print(result);
+      }
+      Expect.isTrue(result.isEquivalent, toStringFunction(member));
+    }
+  }
+
+  void testProcedures(Iterable<Procedure> procedures) {
+    testMembers<Procedure>(
+        procedures,
+        (cloner, procedure) => cloner.cloneProcedure(procedure, null),
+        (procedure) => "${procedure.runtimeType}(${procedure.name}):"
+            "${procedure.function.body}");
+  }
+
+  void testFields(Iterable<Field> fields) {
+    testMembers<Field>(
+        fields,
+        (cloner, field) => cloner.cloneField(field, null, null),
+        (field) => "${field.runtimeType}(${field.name}):"
+            "${field.initializer}");
+  }
+
+  void testConstructors(Iterable<Constructor> constructors) {
+    testMembers<Constructor>(
+        constructors,
+        (cloner, constructor) => cloner.cloneConstructor(constructor, null),
+        (constructor) => "${constructor.runtimeType}(${constructor.name}):"
+            "${constructor.initializers}:"
+            "${constructor.function.body}");
+  }
+
+  void testRedirectingFactories(
+      Iterable<RedirectingFactory> redirectingFactory) {
+    testMembers<RedirectingFactory>(
+        redirectingFactory,
+        (cloner, redirectingFactory) =>
+            cloner.cloneRedirectingFactory(redirectingFactory, null),
+        (redirectingFactory) =>
+            "${redirectingFactory.runtimeType}(${redirectingFactory.name}):"
+            "${redirectingFactory.function.body}");
+  }
+
+  for (Library library in component.libraries) {
+    testProcedures(library.procedures);
+    testFields(library.fields);
+    for (Class cls in library.classes) {
+      testProcedures(cls.procedures);
+      testFields(cls.fields);
+      testConstructors(cls.constructors);
+      testRedirectingFactories(cls.redirectingFactories);
+    }
+  }
+  Expect.isEmpty(
+      creator.createdKinds.toSet()..removeAll(coverageVisitor.visited),
+      'Nodes not covered in testing.');
+}
+
+class MemberEquivalenceStrategy extends EquivalenceStrategy {
+  const MemberEquivalenceStrategy();
+
+  void assumeClonedReferences(EquivalenceVisitor visitor, Member member1,
+      Reference? reference1, Member member2, Reference? reference2) {
+    if (reference1 != null && reference2 != null) {
+      ReferenceName referenceName1 = ReferenceName.fromNamedNode(member1);
+      ReferenceName referenceName2 = ReferenceName.fromNamedNode(member2);
+      if (referenceName1.memberName == referenceName2.memberName &&
+              referenceName1.memberUri == referenceName2.memberUri &&
+              referenceName2.declarationName == null ||
+          referenceName2.libraryUri == null) {
+        visitor.assumeReferences(reference1, reference2);
+      }
+    }
+  }
+
+  @override
+  bool checkProcedure(
+      EquivalenceVisitor visitor, Procedure? node, Object? other) {
+    if (node is Procedure && other is Procedure) {
+      assumeClonedReferences(
+          visitor, node, node.reference, other, other.reference);
+    }
+    return super.checkProcedure(visitor, node, other);
+  }
+
+  @override
+  bool checkConstructor(
+      EquivalenceVisitor visitor, Constructor? node, Object? other) {
+    if (node is Constructor && other is Constructor) {
+      assumeClonedReferences(
+          visitor, node, node.reference, other, other.reference);
+    }
+    return super.checkConstructor(visitor, node, other);
+  }
+
+  @override
+  bool checkRedirectingFactory(
+      EquivalenceVisitor visitor, RedirectingFactory? node, Object? other) {
+    if (node is RedirectingFactory && other is RedirectingFactory) {
+      assumeClonedReferences(
+          visitor, node, node.reference, other, other.reference);
+    }
+    return super.checkRedirectingFactory(visitor, node, other);
+  }
+
+  @override
+  bool checkField(EquivalenceVisitor visitor, Field? node, Object? other) {
+    if (node is Field && other is Field) {
+      assumeClonedReferences(
+          visitor, node, node.getterReference, other, other.getterReference);
+      assumeClonedReferences(
+          visitor, node, node.setterReference, other, other.setterReference);
+    }
+    return super.checkField(visitor, node, other);
+  }
+}
diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc
index 8b2adff9..9037441 100644
--- a/runtime/vm/dart_entry.cc
+++ b/runtime/vm/dart_entry.cc
@@ -727,7 +727,7 @@
   return result.ptr();
 }
 
-ObjectPtr DartLibraryCalls::HandleMessage(const Object& handler,
+ObjectPtr DartLibraryCalls::HandleMessage(Dart_Port port_id,
                                           const Instance& message) {
   auto thread = Thread::Current();
   auto zone = thread->zone();
@@ -743,7 +743,7 @@
     args = Array::New(kNumArguments);
     isolate->isolate_object_store()->set_dart_args_2(args);
   }
-  args.SetAt(0, handler);
+  args.SetAt(0, Integer::Handle(zone, Integer::New(port_id)));
   args.SetAt(1, message);
 #if !defined(PRODUCT)
   if (isolate->debugger()->IsStepping()) {
@@ -753,10 +753,9 @@
     isolate->debugger()->SetResumeAction(Debugger::kStepInto);
   }
 #endif
-  const Object& result =
+  const Object& handler =
       Object::Handle(zone, DartEntry::InvokeFunction(function, args));
-  ASSERT(result.IsNull() || result.IsError());
-  return result.ptr();
+  return handler.ptr();
 }
 
 ObjectPtr DartLibraryCalls::DrainMicrotaskQueue() {
diff --git a/runtime/vm/dart_entry.h b/runtime/vm/dart_entry.h
index c69d5c8..6ec9067 100644
--- a/runtime/vm/dart_entry.h
+++ b/runtime/vm/dart_entry.h
@@ -291,12 +291,13 @@
   // Returns the handler if one has been registered for this port id.
   static ObjectPtr LookupHandler(Dart_Port port_id);
 
+  // Returns handler on success, an ErrorPtr on failure, null if can't find
+  // handler for this port id.
+  static ObjectPtr HandleMessage(Dart_Port port_id, const Instance& message);
+
   // Returns a list of open ReceivePorts.
   static ObjectPtr LookupOpenPorts();
 
-  // Returns null on success, an ErrorPtr on failure.
-  static ObjectPtr HandleMessage(const Object& handler,
-                                 const Instance& dart_message);
 
   // Returns null on success, an ErrorPtr on failure.
   static ObjectPtr DrainMicrotaskQueue();
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index b1967f0..31b26f8 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -1307,26 +1307,6 @@
   tbes.CopyArgument(0, "isolateName", I->name());
 #endif
 
-  // If the message is in band we lookup the handler to dispatch to.  If the
-  // receive port was closed, we drop the message without deserializing it.
-  // Illegal port is a special case for artificially enqueued isolate library
-  // messages which are handled in C++ code below.
-  Object& msg_handler = Object::Handle(zone);
-  if (!message->IsOOB() && (message->dest_port() != Message::kIllegalPort)) {
-    msg_handler = DartLibraryCalls::LookupHandler(message->dest_port());
-    if (msg_handler.IsError()) {
-      return ProcessUnhandledException(Error::Cast(msg_handler));
-    }
-    if (msg_handler.IsNull()) {
-      // If the port has been closed then the message will be dropped at this
-      // point. Make sure to post to the delivery failure port in that case.
-      if (message->RedirectToDeliveryFailurePort()) {
-        PortMap::PostMessage(std::move(message));
-      }
-      return kOK;
-    }
-  }
-
   // Parse the message.
   Object& msg_obj = Object::Handle(zone, ReadMessage(thread, message.get()));
   if (msg_obj.IsError()) {
@@ -1414,12 +1394,18 @@
       tbes.CopyArgument(1, "mode", "basic");
     }
 #endif
-    const Object& result =
-        Object::Handle(zone, DartLibraryCalls::HandleMessage(msg_handler, msg));
-    if (result.IsError()) {
-      status = ProcessUnhandledException(Error::Cast(result));
+    const Object& msg_handler = Object::Handle(
+        zone, DartLibraryCalls::HandleMessage(message->dest_port(), msg));
+    if (msg_handler.IsError()) {
+      status = ProcessUnhandledException(Error::Cast(msg_handler));
+    } else if (msg_handler.IsNull()) {
+      // If the port has been closed then the message will be dropped at this
+      // point. Make sure to post to the delivery failure port in that case.
+      if (message->RedirectToDeliveryFailurePort()) {
+        PortMap::PostMessage(std::move(message));
+      }
     } else {
-      ASSERT(result.IsNull());
+      // The handler closure which was used to successfully handle the message.
     }
   }
   return status;
diff --git a/sdk/lib/_internal/vm/lib/isolate_patch.dart b/sdk/lib/_internal/vm/lib/isolate_patch.dart
index 9703179..6d70c2f 100644
--- a/sdk/lib/_internal/vm/lib/isolate_patch.dart
+++ b/sdk/lib/_internal/vm/lib/isolate_patch.dart
@@ -175,14 +175,19 @@
     return _portMap.values.map((e) => e['port']).toList();
   }
 
-  // Called from the VM to dispatch to the handler.
+  // Called from the VM to retrieve  the handler and handle a message.
   @pragma("vm:entry-point", "call")
-  static void _handleMessage(Function handler, var message) {
+  static _handleMessage(int id, var message) {
+    final handler = _portMap[id]?['handler'];
+    if (handler == null) {
+      return null;
+    }
     // TODO(floitsch): this relies on the fact that any exception aborts the
     // VM. Once we have non-fatal global exceptions we need to catch errors
     // so that we can run the immediate callbacks.
     handler(message);
     _runPendingImmediateCallback();
+    return handler;
   }
 
   // Call into the VM to close the VM maintained mappings.
diff --git a/tools/VERSION b/tools/VERSION
index 2034e26..79b34c1 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 27
+PRERELEASE 28
 PRERELEASE_PATCH 0
\ No newline at end of file