[roll] Update third-party dart packages

Roller-URL: https://ci.chromium.org/swarming/task/62fc4d545a034a11?server=chrome-swarming.appspot.com
CQ-Do-Not-Cancel-Tryjobs: true
Change-Id: I3c4bcb64215ec7e284cf60791440d942be4083c4
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/dart-pkg/+/874941
Commit-Queue: GI Roller <global-integration-roller@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/_discoveryapis_commons/BUILD.gn b/_discoveryapis_commons/BUILD.gn
index a157819..268ef6a 100644
--- a/_discoveryapis_commons/BUILD.gn
+++ b/_discoveryapis_commons/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for _discoveryapis_commons-1.0.5
+# This file is generated by package_importer.py for _discoveryapis_commons-1.0.6
 
 import("//build/dart/dart_library.gni")
 
 dart_library("_discoveryapis_commons") {
   package_name = "_discoveryapis_commons"
 
-  language_version = "2.17"
+  language_version = "2.19"
 
   disable_analysis = true
 
diff --git a/_discoveryapis_commons/CHANGELOG.md b/_discoveryapis_commons/CHANGELOG.md
index ebdf88c..310e5c4 100644
--- a/_discoveryapis_commons/CHANGELOG.md
+++ b/_discoveryapis_commons/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.0.6
+
+- Require Dart 2.19 or later.
+- Allow latest `package:http`.
+
 ## 1.0.5
 
 - Drop use of `NullThrownError`.
diff --git a/_discoveryapis_commons/pubspec.yaml b/_discoveryapis_commons/pubspec.yaml
index 3a9916c..463b4f5 100644
--- a/_discoveryapis_commons/pubspec.yaml
+++ b/_discoveryapis_commons/pubspec.yaml
@@ -1,16 +1,16 @@
 name: _discoveryapis_commons
-version: 1.0.5
+version: 1.0.6
 description: Library for use by client APIs generated from Discovery Documents.
 repository: https://github.com/google/googleapis.dart/tree/master/discoveryapis_commons
 
 environment:
-  sdk: '>=2.17.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
-  http: ^0.13.0
+  http: '>=0.13.5 <2.0.0'
   http_parser: ^4.0.0
   meta: ^1.3.0
 
 dev_dependencies:
-  lints: ^2.0.0
+  dart_flutter_team_lints: ^1.0.0
   test: ^1.16.0
diff --git a/_fe_analyzer_shared/BUILD.gn b/_fe_analyzer_shared/BUILD.gn
index b63acb4..7c9f9b1 100644
--- a/_fe_analyzer_shared/BUILD.gn
+++ b/_fe_analyzer_shared/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for _fe_analyzer_shared-54.0.0
+# This file is generated by package_importer.py for _fe_analyzer_shared-58.0.0
 
 import("//build/dart/dart_library.gni")
 
@@ -18,16 +18,22 @@
     "src/base/errors.dart",
     "src/base/syntactic_entity.dart",
     "src/deferred_function_literal_heuristic.dart",
-    "src/exhaustiveness/equal.dart",
     "src/exhaustiveness/exhaustive.dart",
-    "src/exhaustiveness/intersect.dart",
-    "src/exhaustiveness/intersect_empty.dart",
+    "src/exhaustiveness/key.dart",
+    "src/exhaustiveness/path.dart",
     "src/exhaustiveness/profile.dart",
+    "src/exhaustiveness/shared.dart",
     "src/exhaustiveness/space.dart",
     "src/exhaustiveness/static_type.dart",
-    "src/exhaustiveness/static_types.dart",
-    "src/exhaustiveness/subtract.dart",
     "src/exhaustiveness/test_helper.dart",
+    "src/exhaustiveness/types.dart",
+    "src/exhaustiveness/types/bool.dart",
+    "src/exhaustiveness/types/enum.dart",
+    "src/exhaustiveness/types/future_or.dart",
+    "src/exhaustiveness/types/list.dart",
+    "src/exhaustiveness/types/map.dart",
+    "src/exhaustiveness/types/record.dart",
+    "src/exhaustiveness/types/sealed.dart",
     "src/exhaustiveness/witness.dart",
     "src/experiments/errors.dart",
     "src/experiments/flags.dart",
diff --git a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart
index 3bbf1b4..e23a12e 100644
--- a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart
+++ b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_call_counts.dart
@@ -3,9 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart';
 import 'package:_fe_analyzer_shared/src/exhaustiveness/profile.dart' as profile;
-import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
 import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
 
 import '../../test/exhaustiveness/env.dart';
 import '../../test/exhaustiveness/utils.dart';
@@ -21,9 +22,9 @@
   var b = env.createClass('B', inherits: [a]);
   var c = env.createClass('C', inherits: [a]);
   var d = env.createClass('D', inherits: [a]);
-  var t = env.createClass('T', fields: {'w': a, 'x': a, 'y': a, 'z': a});
+  var t = env.createRecordType({'w': a, 'x': a, 'y': a, 'z': a});
 
-  expectExhaustiveOnlyAll(t, [
+  expectExhaustiveOnlyAll(env, t, [
     {'w': b, 'x': b, 'y': b, 'z': b},
     {'w': b, 'x': b, 'y': b, 'z': c},
     {'w': b, 'x': b, 'y': b, 'z': d},
@@ -110,9 +111,11 @@
 
 /// Test that [cases] are exhaustive over [type] if and only if all cases are
 /// included and that all subsets of the cases are not exhaustive.
-void expectExhaustiveOnlyAll(StaticType type, List<Object> cases) {
-  var spaces = parseSpaces(cases);
+void expectExhaustiveOnlyAll(ObjectFieldLookup objectFieldLookup,
+    StaticType type, List<Map<String, Object>> cases) {
+  var spaces = cases.map((c) => ty(type, c)).toList();
   profile.reset();
-  print(isExhaustive(Space(type), spaces));
+  print(
+      isExhaustive(objectFieldLookup, Space(const Path.root(), type), spaces));
   profile.log();
 }
diff --git a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart
index a9613b4..dd3a6f9 100644
--- a/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart
+++ b/_fe_analyzer_shared/benchmark/exhaustiveness/large_fields_timed.dart
@@ -5,6 +5,7 @@
 import 'dart:math';
 
 import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart';
 import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
 import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
 
@@ -20,9 +21,9 @@
   var b = env.createClass('B', inherits: [a]);
   var c = env.createClass('C', inherits: [a]);
   var d = env.createClass('D', inherits: [a]);
-  var t = env.createClass('T', fields: {'w': a, 'x': a, 'y': a, 'z': a});
+  var t = env.createRecordType({'w': a, 'x': a, 'y': a, 'z': a});
 
-  expectExhaustiveOnlyAll(t, [
+  expectExhaustiveOnlyAll(env, t, [
     {'w': b, 'x': b, 'y': b, 'z': b},
     {'w': b, 'x': b, 'y': b, 'z': c},
     {'w': b, 'x': b, 'y': b, 'z': d},
@@ -109,15 +110,18 @@
 
 /// Test that [cases] are exhaustive over [type] if and only if all cases are
 /// included and that all subsets of the cases are not exhaustive.
-void expectExhaustiveOnlyAll(StaticType type, List<Object> cases) {
+void expectExhaustiveOnlyAll(ObjectFieldLookup objectFieldLookup,
+    StaticType type, List<Map<String, Object>> cases) {
+  var valueSpace = Space(const Path.root(), type);
+  var caseSpaces = cases.map((c) => ty(type, c)).toList();
+
   const trials = 100;
 
   var best = 9999999;
   for (var j = 0; j < 100000; j++) {
     var watch = Stopwatch()..start();
     for (var i = 0; i < trials; i++) {
-      var spaces = parseSpaces(cases);
-      var actual = isExhaustive(Space(type), spaces);
+      var actual = isExhaustive(objectFieldLookup, valueSpace, caseSpaces);
       if (!actual) {
         throw 'Expected exhaustive';
       }
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/equal.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/equal.dart
deleted file mode 100644
index 63f527d..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/equal.dart
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2022, 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 'profile.dart' as profile;
-import 'space.dart';
-
-/// Returns `true` if [left] and [right] are equivalent spaces.
-///
-/// Equality is defined purely structurally/syntactically.
-bool equal(Space left, Space right, String reason) {
-  profile.count('equal', reason);
-
-  if (identical(left, right)) return true;
-
-  // Empty is only equal to itself (and will get caught by the previous check).
-  if (left == Space.empty) return false;
-  if (right == Space.empty) return false;
-
-  if (left is UnionSpace && right is UnionSpace) {
-    return _equalUnions(left, right);
-  }
-
-  if (left is ExtractSpace && right is ExtractSpace) {
-    return _equalExtracts(left, right);
-  }
-
-  // If we get here, one is a union and one is an extract.
-  return false;
-}
-
-/// Returns `true` if [left] and [right] have the same type and the same fields
-/// with equal subspaces.
-bool _equalExtracts(ExtractSpace left, ExtractSpace right) {
-  // Must have the same type.
-  if (left.type != right.type) return false;
-
-  // And the same fields.
-  Set<String> fields = {...left.fields.keys, ...right.fields.keys};
-  if (left.fields.length != fields.length) return false;
-  if (right.fields.length != fields.length) return false;
-
-  for (String field in fields) {
-    if (!equal(left.fields[field]!, right.fields[field]!, 'recurse extract')) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-/// Returns `true` if [left] and [right] contain equal arms in any order.
-///
-/// Assumes that all duplicates have already been removed from each union.
-bool _equalUnions(UnionSpace left, UnionSpace right) {
-  if (left.arms.length != right.arms.length) return false;
-
-  /// For each left arm, should find an equal right arm.
-  for (Space leftArm in left.arms) {
-    bool found = false;
-    for (Space rightArm in right.arms) {
-      if (equal(leftArm, rightArm, 'recurse union')) {
-        found = true;
-        break;
-      }
-    }
-    if (!found) return false;
-  }
-
-  return true;
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart
index 2522a91..afb6bec 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/exhaustive.dart
@@ -2,19 +2,18 @@
 // 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 'space.dart';
 import 'static_type.dart';
-import 'subtract.dart';
+import 'key.dart';
+import 'path.dart';
+import 'profile.dart' as profile;
+import 'space.dart';
 import 'witness.dart';
 
-/// Returns `true` if [cases] exhaustively covers all possible values of
-/// [value].
-///
-/// This is defined simply in terms of subtraction and unions: [cases] is a
-/// union space, and it's exhaustive if subtracting it from [value] leaves
-/// nothing.
-bool isExhaustive(Space value, List<Space> cases) {
-  return subtract(value, new Space.union(cases)) == Space.empty;
+/// Returns `true` if [caseSpaces] exhaustively covers all possible values of
+/// [valueSpace].
+bool isExhaustive(
+    ObjectFieldLookup fieldLookup, Space valueSpace, List<Space> caseSpaces) {
+  return checkExhaustiveness(fieldLookup, valueSpace, caseSpaces) == null;
 }
 
 /// Checks the [cases] representing a series of switch cases to see if they
@@ -22,119 +21,274 @@
 /// checks to see if any case can't be matched because it's covered by previous
 /// cases.
 ///
-/// Returns a string containing any unreachable case or non-exhaustive match
-/// errors. Returns an empty string if all cases are reachable and the cases
-/// are exhaustive.
+/// Returns a list of any unreachable case or non-exhaustive match errors.
+/// Returns an empty list if all cases are reachable and the cases are
+/// exhaustive.
+List<ExhaustivenessError> reportErrors(
+    ObjectFieldLookup fieldLookup, StaticType valueType, List<Space> cases) {
+  _Checker checker = new _Checker(fieldLookup);
 
-List<ExhaustivenessError> reportErrors(StaticType valueType, List<Space> cases,
-    [List<Space>? remainingSpaces]) {
-  return reportErrorsNew(valueType, cases);
-}
-
-List<ExhaustivenessError> reportErrorsOld(
-    StaticType valueType, List<Space> cases,
-    [List<Space>? remainingSpaces]) {
   List<ExhaustivenessError> errors = <ExhaustivenessError>[];
 
-  Space remaining = new Space(valueType);
-  for (int i = 0; i < cases.length; i++) {
+  Space valuePattern = new Space(const Path.root(), valueType);
+  List<List<Space>> caseRows = cases.map((space) => [space]).toList();
+
+  for (int i = 1; i < caseRows.length; i++) {
     // See if this case is covered by previous ones.
-    if (i > 0) {
-      Space previous = new Space.union(cases.sublist(0, i));
-      if (subtract(cases[i], previous) == Space.empty) {
-        errors.add(new UnreachableCaseErrorOld(valueType, cases, i, previous));
-      }
+    if (checker._unmatched(caseRows.sublist(0, i), caseRows[i]) == null) {
+      errors.add(new UnreachableCaseError(valueType, cases, i));
     }
-
-    remainingSpaces?.add(remaining);
-    remaining = subtract(remaining, cases[i]);
   }
-  remainingSpaces?.add(remaining);
 
-  if (remaining != Space.empty) {
-    errors.add(new NonExhaustiveErrorOld(valueType, cases, remaining));
+  Witness? witness = checker._unmatched(caseRows, [valuePattern]);
+  if (witness != null) {
+    errors.add(new NonExhaustiveError(valueType, cases, witness));
   }
 
   return errors;
 }
 
+/// Determines if [cases] is exhaustive over all values contained by
+/// [valueSpace]. If so, returns `null`. Otherwise, returns a string describing
+/// an example of one value that isn't matched by anything in [cases].
+Witness? checkExhaustiveness(
+    ObjectFieldLookup fieldLookup, Space valueSpace, List<Space> cases) {
+  _Checker checker = new _Checker(fieldLookup);
+
+  // TODO(johnniwinther): Perform reachability checking.
+  List<List<Space>> caseRows = cases.map((space) => [space]).toList();
+
+  Witness? witness = checker._unmatched(caseRows, [valueSpace]);
+
+  // Uncomment this to have it print out the witness for non-exhaustive matches.
+  // if (witness != null) print(witness);
+
+  return witness;
+}
+
+class _Checker {
+  final ObjectFieldLookup _fieldLookup;
+
+  _Checker(this._fieldLookup);
+
+  /// Tries to find a pattern containing at least one value matched by
+  /// [valuePatterns] that is not matched by any of the patterns in [caseRows].
+  ///
+  /// If found, returns it. This is a witness example showing that [caseRows] is
+  /// not exhaustive over all values in [valuePatterns]. If it returns `null`,
+  /// then [caseRows] exhaustively covers [valuePatterns].
+  Witness? _unmatched(List<List<Space>> caseRows, List<Space> valuePatterns,
+      [List<Predicate> witnessPredicates = const []]) {
+    assert(caseRows.every((element) => element.length == valuePatterns.length),
+        "Value patterns: $valuePatterns, case rows: $caseRows.");
+    profile.count('_unmatched');
+    // If there are no more columns, then we've tested all the predicates we
+    // have to test.
+    if (valuePatterns.isEmpty) {
+      // If there are still any rows left, then it means every remaining value
+      // will go to one of those rows' bodies, so we have successfully matched.
+      if (caseRows.isNotEmpty) return null;
+
+      // If we ran out of rows too, then it means [witnessPredicates] is now a
+      // complete description of at least one value that slipped past all the
+      // rows.
+      return new Witness(witnessPredicates);
+    }
+
+    // Look down the first column of tests.
+    Space firstValuePatterns = valuePatterns[0];
+
+    Set<Key> keysOfInterest = {};
+    for (List<Space> caseRow in caseRows) {
+      for (SingleSpace singleSpace in caseRow.first.singleSpaces) {
+        keysOfInterest.addAll(singleSpace.additionalFields.keys);
+      }
+    }
+    for (SingleSpace firstValuePattern in firstValuePatterns.singleSpaces) {
+      // TODO(johnniwinther): Right now, this brute force expands all subtypes
+      // of sealed types and considers them individually. It would be faster to
+      // look at the types of the patterns in the first column of each row and
+      // only expand subtypes that are actually tested.
+      // Split the type into its sealed subtypes and consider each one
+      // separately. This enables it to filter rows more effectively.
+      List<StaticType> subtypes =
+          expandSealedSubtypes(firstValuePattern.type, keysOfInterest);
+      for (StaticType subtype in subtypes) {
+        Witness? result = _filterByType(subtype, caseRows, firstValuePattern,
+            valuePatterns, witnessPredicates, firstValuePatterns.path);
+
+        // If we found a witness for a subtype that no rows match, then we can
+        // stop. There may be others but we don't need to find more.
+        if (result != null) return result;
+      }
+    }
+
+    // If we get here, no subtype yielded a witness, so we must have matched
+    // everything.
+    return null;
+  }
+
+  Witness? _filterByType(
+      StaticType type,
+      List<List<Space>> caseRows,
+      SingleSpace firstSingleSpaceValue,
+      List<Space> valueSpaces,
+      List<Predicate> witnessPredicates,
+      Path path) {
+    profile.count('_filterByType');
+    // Extend the witness with the type we're matching.
+    List<Predicate> extendedWitness = [
+      ...witnessPredicates,
+      new Predicate(path, type)
+    ];
+
+    // 1) Discard any rows that might not match because the column's type isn't
+    // a subtype of the value's type.  We only keep rows that *must* match
+    // because a row that could potentially fail to match will not help us prove
+    // exhaustiveness.
+    //
+    // 2) Expand any unions in the first column. This can (deliberately) produce
+    // duplicate rows in remainingRows.
+    List<SingleSpace> remainingRowFirstSingleSpaces = [];
+    List<List<Space>> remainingRows = [];
+    for (List<Space> row in caseRows) {
+      Space firstSpace = row[0];
+
+      for (SingleSpace firstSingleSpace in firstSpace.singleSpaces) {
+        // If the row's type is a supertype of the value pattern's type then it
+        // must match.
+        if (type.isSubtypeOf(firstSingleSpace.type)) {
+          remainingRowFirstSingleSpaces.add(firstSingleSpace);
+          remainingRows.add(row);
+        }
+      }
+    }
+
+    // We have now filtered by the type test of the first column of patterns,
+    // but some of those may also have field subpatterns. If so, lift those out
+    // so we can recurse into them.
+    Set<Key> fieldKeys = {
+      ...firstSingleSpaceValue.fields.keys,
+      for (SingleSpace firstPattern in remainingRowFirstSingleSpaces)
+        ...firstPattern.fields.keys
+    };
+
+    Set<Key> additionalFieldKeys = {
+      ...firstSingleSpaceValue.additionalFields.keys,
+      for (SingleSpace firstPattern in remainingRowFirstSingleSpaces)
+        ...firstPattern.additionalFields.keys
+    };
+
+    // Sorting isn't necessary, but makes the behavior deterministic.
+    List<Key> sortedFieldKeys = fieldKeys.toList()..sort();
+    List<Key> sortedAdditionalFieldKeys = additionalFieldKeys.toList()..sort();
+
+    // Remove the first column from the value list and replace it with any
+    // expanded fields.
+    valueSpaces = [
+      ..._expandFields(sortedFieldKeys, sortedAdditionalFieldKeys,
+          firstSingleSpaceValue, type, path),
+      ...valueSpaces.skip(1)
+    ];
+
+    // Remove the first column from each row and replace it with any expanded
+    // fields.
+    for (int i = 0; i < remainingRows.length; i++) {
+      remainingRows[i] = [
+        ..._expandFields(
+            sortedFieldKeys,
+            sortedAdditionalFieldKeys,
+            remainingRowFirstSingleSpaces[i],
+            remainingRowFirstSingleSpaces[i].type,
+            path),
+        ...remainingRows[i].skip(1)
+      ];
+    }
+
+    // Proceed to the next column.
+    return _unmatched(remainingRows, valueSpaces, extendedWitness);
+  }
+
+  /// Given a list of [fieldKeys] and [additionalFieldKeys], and a
+  /// [singleSpace], generates a list of single spaces, one for each named field
+  /// and additional field key.
+  ///
+  /// When [singleSpace] contains a field with that name or an additional field
+  /// with the key, extracts it into the resulting list. Otherwise, the
+  /// [singleSpace] doesn't care about that field, so inserts a default [Space]
+  /// that matches all values for the field.
+  ///
+  /// In other words, this unpacks a set of fields so that the main algorithm
+  /// can add them to the worklist.
+  List<Space> _expandFields(List<Key> fieldKeys, List<Key> additionalFieldKeys,
+      SingleSpace singleSpace, StaticType type, Path path) {
+    profile.count('_expandFields');
+    List<Space> result = <Space>[];
+    for (Key key in fieldKeys) {
+      Space? field = singleSpace.fields[key];
+      if (field != null) {
+        result.add(field);
+      } else {
+        // This pattern doesn't test this field, so add a pattern for the
+        // field that matches all values. This way the columns stay aligned.
+        result.add(new Space(path.add(key),
+            type.getField(_fieldLookup, key) ?? StaticType.nullableObject));
+      }
+    }
+    for (Key key in additionalFieldKeys) {
+      Space? field = singleSpace.additionalFields[key];
+      if (field != null) {
+        result.add(field);
+      } else {
+        // This pattern doesn't test this field, so add a pattern for the
+        // field that matches all values. This way the columns stay aligned.
+        result.add(new Space(path.add(key),
+            type.getAdditionalField(key) ?? StaticType.nullableObject));
+      }
+    }
+    return result;
+  }
+}
+
+/// Recursively expands [type] with its subtypes if its sealed.
+///
+/// Otherwise, just returns [type].
+List<StaticType> expandSealedSubtypes(
+    StaticType type, Set<Key> keysOfInterest) {
+  profile.count('expandSealedSubtypes');
+  if (!type.isSealed) {
+    return [type];
+  } else {
+    return {
+      for (StaticType subtype in type.getSubtypes(keysOfInterest))
+        ...expandSealedSubtypes(subtype, keysOfInterest)
+    }.toList();
+  }
+}
+
 class ExhaustivenessError {}
 
-abstract class NonExhaustiveError implements ExhaustivenessError {
-  StaticType get valueType;
-  List<Space> get cases;
-  String get witness;
-}
-
-class NonExhaustiveErrorNew implements NonExhaustiveError {
-  @override
+class NonExhaustiveError implements ExhaustivenessError {
   final StaticType valueType;
 
-  @override
   final List<Space> cases;
 
-  @override
-  final String witness;
+  final Witness witness;
 
-  NonExhaustiveErrorNew(this.valueType, this.cases, this.witness);
+  NonExhaustiveError(this.valueType, this.cases, this.witness);
 
   @override
   String toString() =>
-      '$valueType is not exhaustively matched by ${new Space.union(cases)}.';
+      '$valueType is not exhaustively matched by ${cases.join('|')}.';
 }
 
-class NonExhaustiveErrorOld implements NonExhaustiveError {
-  @override
+class UnreachableCaseError implements ExhaustivenessError {
   final StaticType valueType;
-
-  @override
   final List<Space> cases;
-
-  final Space remaining;
-
-  NonExhaustiveErrorOld(this.valueType, this.cases, this.remaining);
-
-  @override
-  String get witness => '$remaining';
-
-  @override
-  String toString() =>
-      '$valueType is not exhaustively matched by ${new Space.union(cases)}.';
-}
-
-abstract class UnreachableCaseError implements ExhaustivenessError {
-  StaticType get valueType;
-  List<Space> get cases;
-  int get index;
-}
-
-class UnreachableCaseErrorNew implements UnreachableCaseError {
-  @override
-  final StaticType valueType;
-  @override
-  final List<Space> cases;
-  @override
   final int index;
 
-  UnreachableCaseErrorNew(this.valueType, this.cases, this.index);
+  UnreachableCaseError(this.valueType, this.cases, this.index);
 
   @override
   String toString() => 'Case #${index + 1} ${cases[index]} is unreachable.';
 }
-
-class UnreachableCaseErrorOld implements UnreachableCaseError {
-  @override
-  final StaticType valueType;
-  @override
-  final List<Space> cases;
-  @override
-  final int index;
-  final Space previous;
-
-  UnreachableCaseErrorOld(
-      this.valueType, this.cases, this.index, this.previous);
-
-  @override
-  String toString() =>
-      'Case #${index + 1} ${cases[index]} is covered by $previous.';
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/intersect.dart
deleted file mode 100644
index 57a6dfa..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect.dart
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2022, 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 'profile.dart' as profile;
-import 'space.dart';
-import 'static_type.dart';
-
-/// Calculates the intersection of [left] and [right].
-///
-/// This is used to tell if two field spaces on a pair of spaces being
-/// subtracted have no common values.
-Space intersect(Space left, Space right) {
-  profile.count('intersect');
-
-  // The intersection with an empty space is always empty.
-  if (left == Space.empty) return Space.empty;
-  if (right == Space.empty) return Space.empty;
-
-  // The intersection of a union is the union of the intersections of its arms.
-  if (left is UnionSpace) {
-    return new Space.union(
-        left.arms.map((arm) => intersect(arm, right)).toList());
-  }
-
-  if (right is UnionSpace) {
-    return new Space.union(
-        right.arms.map((arm) => intersect(left, arm)).toList());
-  }
-
-  // Otherwise, we're intersecting two [ExtractSpaces].
-  return _intersectExtracts(left as ExtractSpace, right as ExtractSpace);
-}
-
-/// Returns the intersection of two static types [left] and [right].
-///
-/// Returns `StaticType.neverType` if the intersection is empty.
-StaticType intersectTypes(StaticType left, StaticType right) {
-  // If one type is a subtype, the subtype is the intersection.
-  if (left.isSubtypeOf(right)) return left;
-  if (right.isSubtypeOf(left)) return right;
-
-  if (left is NullableStaticType) {
-    if (right is NullableStaticType) {
-      StaticType intersection =
-          intersectTypes(left.underlying, right.underlying);
-      if (intersection == StaticType.neverType) return StaticType.neverType;
-      return intersection.nullable;
-    } else {
-      return intersectTypes(left.underlying, right);
-    }
-  } else if (right is NullableStaticType) {
-    return intersectTypes(left, right.underlying);
-  }
-
-  // If we allow sealed types to share subtypes, then this will need to be more
-  // sophisticated. Here:
-  //
-  //   (A) (B)
-  //   / \ / \
-  //  C   D   E
-  //
-  // The intersection of A and B should be D. Here:
-  //
-  //  (A)   (B)
-  //   | \  / |
-  //   |\ \/ /|
-  //   | \/\/ |
-  //   C D  E F
-  //
-  // It should be D, E.
-
-  // Unrelated types.
-  return StaticType.neverType;
-}
-
-/// Returns the interaction of extract spaces [left] and [right].
-Space _intersectExtracts(ExtractSpace left, ExtractSpace right) {
-  StaticType type = intersectTypes(left.type, right.type);
-
-  // If the types are disjoint, the intersection is empty.
-  if (type == StaticType.neverType) return Space.empty;
-
-  // Recursively intersect the fields.
-  List<String> fieldNames =
-      {...left.fields.keys, ...right.fields.keys}.toList();
-
-  // Sorting isn't needed for correctness, just to make the tests less brittle.
-  fieldNames.sort();
-
-  Map<String, Space> fields = <String, Space>{};
-  for (String name in fieldNames) {
-    Space field = _intersectFields(left.fields[name], right.fields[name]);
-
-    // If the fields are disjoint, then the entire space will have no values.
-    if (field == Space.empty) return Space.empty;
-    fields[name] = field;
-  }
-
-  return new Space(type, fields);
-}
-
-Space _intersectFields(Space? left, Space? right) {
-  if (left == null) return right!;
-  if (right == null) return left;
-  return intersect(left, right);
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect_empty.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/intersect_empty.dart
deleted file mode 100644
index b756365..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/intersect_empty.dart
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2022, 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 'space.dart';
-import 'static_type.dart';
-
-/// Calculates whether the intersection of [left] and [right] is empty.
-///
-/// This is used to tell if two field spaces on a pair of spaces being
-/// subtracted have no common values.
-bool spacesHaveEmptyIntersection(Space left, Space right) {
-  // The intersection with an empty space is always empty.
-  if (left == Space.empty) return true;
-  if (right == Space.empty) return true;
-
-  // The intersection of a union is empty if all of the arms are.
-  if (left is UnionSpace) {
-    return left.arms.every((arm) => spacesHaveEmptyIntersection(arm, right));
-  }
-
-  if (right is UnionSpace) {
-    return right.arms.every((arm) => spacesHaveEmptyIntersection(left, arm));
-  }
-
-  // Otherwise, we're intersecting two [ExtractSpaces].
-  return _extractsHaveEmptyIntersection(
-      left as ExtractSpace, right as ExtractSpace);
-}
-
-/// Returns true if the intersection of two static types [left] and [right] is
-/// empty.
-bool typesHaveEmptyIntersection(StaticType left, StaticType right) {
-  // If one type is a subtype, the subtype is the intersection.
-  if (left.isSubtypeOf(right)) return false;
-  if (right.isSubtypeOf(left)) return false;
-
-  // Unrelated types.
-  return true;
-}
-
-/// Returns the interaction of extract spaces [left] and [right].
-bool _extractsHaveEmptyIntersection(ExtractSpace left, ExtractSpace right) {
-  if (typesHaveEmptyIntersection(left.type, right.type)) return true;
-
-  // Recursively intersect the fields.
-  List<String> fieldNames =
-      {...left.fields.keys, ...right.fields.keys}.toList();
-  for (String name in fieldNames) {
-    // If the fields are disjoint, then the entire space will have no values.
-    if (_fieldsHaveEmptyIntersection(left.fields[name], right.fields[name])) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool _fieldsHaveEmptyIntersection(Space? left, Space? right) {
-  if (left == null) return right! == Space.empty;
-  if (right == null) return left == Space.empty;
-  return spacesHaveEmptyIntersection(left, right);
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/key.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/key.dart
new file mode 100644
index 0000000..1f7fefc
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/key.dart
@@ -0,0 +1,223 @@
+// Copyright (c) 2023, 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.
+
+/// A value that defines the key of an additional field.
+///
+/// This is used for accessing entries in maps and elements in lists.
+abstract class Key implements Comparable<Key> {
+  String get name;
+}
+
+/// An entry in a map whose key is a constant [value].
+class MapKey extends Key {
+  final Object value;
+  final String valueAsText;
+
+  MapKey(this.value, this.valueAsText);
+
+  @override
+  String get name => '[$valueAsText]';
+
+  @override
+  int get hashCode => value.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is MapKey && value == other.value;
+  }
+
+  @override
+  String toString() => valueAsText;
+
+  @override
+  int compareTo(Key other) {
+    if (other is MapKey) {
+      return valueAsText.compareTo(other.valueAsText);
+    } else if (other is HeadKey || other is RestKey || other is TailKey) {
+      // Map keys after list keys.
+      return 1;
+    } else {
+      // Map keys before record index and name keys,
+      return -1;
+    }
+  }
+}
+
+/// Tagging interface for the list specific keys [HeadKey], [RestKey], and
+/// [TailKey].
+abstract class ListKey implements Key {}
+
+/// An element in a list accessed by an [index] from the start of the list.
+class HeadKey extends Key implements ListKey {
+  final int index;
+
+  HeadKey(this.index);
+
+  @override
+  String get name => '[$index]';
+
+  @override
+  int get hashCode => index.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is HeadKey && index == other.index;
+  }
+
+  @override
+  String toString() => 'HeadKey($index)';
+
+  @override
+  int compareTo(Key other) {
+    if (other is HeadKey) {
+      return index.compareTo(other.index);
+    } else {
+      // Head keys before other keys,
+      return -1;
+    }
+  }
+}
+
+/// An element in a list accessed by an [index] from the end of the list, that
+/// is, the [index]th last element.
+class TailKey extends Key implements ListKey {
+  final int index;
+
+  TailKey(this.index);
+
+  @override
+  String get name => '[${-(index + 1)}]';
+
+  @override
+  int get hashCode => index.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is TailKey && index == other.index;
+  }
+
+  @override
+  String toString() => 'TailKey($index)';
+
+  @override
+  int compareTo(Key other) {
+    if (other is TailKey) {
+      return -index.compareTo(other.index);
+    } else if (other is HeadKey || other is RestKey) {
+      // Tail keys after head and rest keys.
+      return 1;
+    } else {
+      // Tail keys before map, record index and name keys,
+      return -1;
+    }
+  }
+}
+
+/// A sublist of a list from the [headSize]th index to the [tailSize]th last
+/// index.
+class RestKey extends Key implements ListKey {
+  final int headSize;
+  final int tailSize;
+
+  RestKey(this.headSize, this.tailSize);
+
+  @override
+  String get name => '[$headSize:${-tailSize}]';
+
+  @override
+  int get hashCode => Object.hash(headSize, tailSize);
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is RestKey &&
+        headSize == other.headSize &&
+        tailSize == other.tailSize;
+  }
+
+  @override
+  String toString() => 'RestKey($headSize,$tailSize)';
+
+  @override
+  int compareTo(Key other) {
+    if (other is RestKey) {
+      int result = headSize.compareTo(other.headSize);
+      if (result == 0) {
+        result = -tailSize.compareTo(other.tailSize);
+      }
+      return result;
+    } else if (other is HeadKey) {
+      // Rest keys after head keys.
+      return 1;
+    } else {
+      // Rest keys before tail, map, record index and name keys,
+      return -1;
+    }
+  }
+}
+
+/// Key for a regular object member.
+class NameKey extends Key {
+  @override
+  final String name;
+
+  NameKey(this.name);
+
+  @override
+  int get hashCode => name.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is NameKey && name == other.name;
+  }
+
+  @override
+  String toString() => 'NameKey($name)';
+
+  @override
+  int compareTo(Key other) {
+    if (other is RecordIndexKey) {
+      // Name keys after record index keys.
+      return 1;
+    } else if (other is NameKey) {
+      return name.compareTo(other.name);
+    } else {
+      // Name keys after other keys.
+      return 1;
+    }
+  }
+}
+
+/// Tagging interface for the record specific keys [RecordIndexKey] and
+/// [RecordNameKey].
+abstract class RecordKey implements Key {}
+
+/// Specialized [NameKey] for an indexed record field.
+class RecordIndexKey extends NameKey implements RecordKey {
+  final int index;
+
+  RecordIndexKey(this.index) : super('\$${index + 1}');
+
+  @override
+  int compareTo(Key other) {
+    if (other is RecordIndexKey) {
+      return index.compareTo(other.index);
+    } else if (other is NameKey) {
+      // Record index keys before name keys.
+      return -1;
+    } else {
+      // Record index keys after other keys.
+      return 1;
+    }
+  }
+}
+
+/// Specialized [NameKey] for a named record field.
+class RecordNameKey extends NameKey implements RecordKey {
+  RecordNameKey(super.name);
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/path.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/path.dart
new file mode 100644
index 0000000..364b177
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/path.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2023, 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 'key.dart';
+
+/// A path that describes location of a [SingleSpace] from the root of
+/// enclosing [Space].
+abstract class Path {
+  const Path();
+
+  /// Create root path.
+  const factory Path.root() = _Root;
+
+  /// Returns a path that adds a step by the [key] to the current path.
+  Path add(Key key) => new _Step(this, key);
+
+  void _toList(List<Key> list);
+
+  /// Returns a list of the keys from the root to this path.
+  List<Key> toList();
+}
+
+/// The root path object.
+class _Root extends Path {
+  const _Root();
+
+  @override
+  void _toList(List<Key> list) {}
+
+  @override
+  List<Key> toList() => const [];
+
+  @override
+  int get hashCode => 1729;
+
+  @override
+  bool operator ==(Object other) {
+    return other is _Root;
+  }
+
+  @override
+  String toString() => '@';
+}
+
+/// A single step in a path that holds the [parent] pointer and the [key] for
+/// the step.
+class _Step extends Path {
+  final Path parent;
+  final Key key;
+
+  _Step(this.parent, this.key);
+
+  @override
+  List<Key> toList() {
+    List<Key> list = [];
+    _toList(list);
+    return list;
+  }
+
+  @override
+  void _toList(List<Key> list) {
+    parent._toList(list);
+    list.add(key);
+  }
+
+  @override
+  late final int hashCode = Object.hash(parent, key);
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is _Step && key == other.key && parent == other.parent;
+  }
+
+  @override
+  String toString() {
+    if (parent is _Root) {
+      return key.name;
+    } else {
+      return '$parent.${key.name}';
+    }
+  }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart
new file mode 100644
index 0000000..d3a7048
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart
@@ -0,0 +1,715 @@
+// Copyright (c) 2023, 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 'exhaustive.dart';
+import 'key.dart';
+import 'path.dart';
+import 'space.dart';
+import 'static_type.dart';
+import 'types.dart';
+
+/// Interface implemented by analyze/CFE to support type operations need for the
+/// shared [StaticType]s.
+abstract class TypeOperations<Type extends Object> {
+  /// Returns the type for `Object?`.
+  Type get nullableObjectType;
+
+  /// Returns the type for the non-nullable `Object`.
+  Type get nonNullableObjectType;
+
+  /// Returns `true` if [s] is a subtype of [t].
+  bool isSubtypeOf(Type s, Type t);
+
+  /// Returns a type that overapproximates the possible values of [type] by
+  /// replacing all type variables with the default types.
+  Type overapproximate(Type type);
+
+  /// Returns `true` if [type] is a potentially nullable type.
+  bool isNullable(Type type);
+
+  /// Returns the non-nullable type corresponding to [type]. For instance
+  /// `Foo` for `Foo?`. If [type] is already non-nullable, it itself is
+  /// returned.
+  Type getNonNullable(Type type);
+
+  /// Returns `true` if [type] is the `Null` type.
+  bool isNullType(Type type);
+
+  /// Returns `true` if [type] is the `Never` type.
+  bool isNeverType(Type type);
+
+  /// Returns `true` if [type] is the `Object?` type.
+  bool isNullableObject(Type type);
+
+  /// Returns `true` if [type] is the `Object` type.
+  bool isNonNullableObject(Type type);
+
+  /// Returns `true` if [type] is the `dynamic` type.
+  bool isDynamic(Type type);
+
+  /// Returns `true` if [type] is the `bool` type.
+  bool isBoolType(Type type);
+
+  /// Returns the `bool` type.
+  Type get boolType;
+
+  /// Returns `true` if [type] is a record type.
+  bool isRecordType(Type type);
+
+  /// Returns `true` if [type] is a generic interface type.
+  bool isGeneric(Type type);
+
+  /// Returns the type `T` if [type] is `FutureOr<T>`. Returns `null` otherwise.
+  Type? getFutureOrTypeArgument(Type type);
+
+  /// Returns the non-nullable type `Future<T>` for [type] `T`.
+  Type instantiateFuture(Type type);
+
+  /// Returns a map of the field names and corresponding types available on
+  /// [type]. For an interface type, these are the fields and getters, and for
+  /// record types these are the record fields.
+  Map<Key, Type> getFieldTypes(Type type);
+
+  /// Returns the value type `V` if [type] implements `Map<K, V>` or `null`
+  /// otherwise.
+  Type? getMapValueType(Type type);
+
+  /// Returns the element type `E` if [type] implements `List<E>` or `null`
+  /// otherwise.
+  Type? getListElementType(Type type);
+
+  /// Returns the list type `List<E>` if [type] implements `List<E>` or `null`
+  /// otherwise.
+  Type? getListType(Type type);
+
+  /// Returns a human-readable representation of the [type].
+  String typeToString(Type type);
+
+  /// Returns `true` if [type] has a simple name that can be used as the type
+  /// of an object pattern.
+  bool hasSimpleName(Type type);
+}
+
+/// Interface for looking up fields and their corresponding [StaticType]s of
+/// a given type.
+abstract class FieldLookup<Type extends Object> {
+  /// Returns a map of the field names and corresponding [StaticType]s available
+  /// on [type]. For an interface type, these are the fields and getters, and
+  /// for record types these are the record fields.
+  Map<Key, StaticType> getFieldTypes(Type type);
+
+  StaticType? getAdditionalFieldType(Type type, Key key);
+}
+
+/// Cache used for computing [StaticType]s used for exhaustiveness checking.
+///
+/// This implementation is shared between analyzer and CFE, and implemented
+/// using the analyzer/CFE implementations of [TypeOperations],
+/// [EnumOperations], and [SealedClassOperations].
+class ExhaustivenessCache<
+        Type extends Object,
+        Class extends Object,
+        EnumClass extends Object,
+        EnumElement extends Object,
+        EnumElementValue extends Object>
+    implements FieldLookup<Type>, ObjectFieldLookup {
+  final TypeOperations<Type> typeOperations;
+  final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
+      enumOperations;
+  final SealedClassOperations<Type, Class> _sealedClassOperations;
+
+  /// Cache for [EnumInfo] for enum classes.
+  Map<EnumClass, EnumInfo<Type, EnumClass, EnumElement, EnumElementValue>>
+      _enumInfo = {};
+
+  /// Cache for [SealedClassInfo] for sealed classes.
+  Map<Class, SealedClassInfo<Type, Class>> _sealedClassInfo = {};
+
+  /// Cache for [UniqueStaticType]s.
+  Map<Object, StaticType> _uniqueTypeMap = {};
+
+  /// Cache for the [StaticType] for `bool`.
+  late BoolStaticType _boolStaticType =
+      new BoolStaticType(typeOperations, this, typeOperations.boolType);
+
+  /// Cache for [StaticType]s for fields available on a [Type].
+  Map<Type, Map<Key, StaticType>> _fieldCache = {};
+
+  ExhaustivenessCache(
+      this.typeOperations, this.enumOperations, this._sealedClassOperations);
+
+  /// Returns the [EnumInfo] for [enumClass].
+  EnumInfo<Type, EnumClass, EnumElement, EnumElementValue> _getEnumInfo(
+      EnumClass enumClass) {
+    return _enumInfo[enumClass] ??=
+        new EnumInfo(typeOperations, this, enumOperations, enumClass);
+  }
+
+  /// Returns the [SealedClassInfo] for [sealedClass].
+  SealedClassInfo<Type, Class> _getSealedClassInfo(Class sealedClass) {
+    return _sealedClassInfo[sealedClass] ??=
+        new SealedClassInfo(_sealedClassOperations, sealedClass);
+  }
+
+  /// Returns the [StaticType] for the boolean [value].
+  StaticType getBoolValueStaticType(bool value) {
+    return value ? _boolStaticType.trueType : _boolStaticType.falseType;
+  }
+
+  /// Returns the [StaticType] for [type].
+  StaticType getStaticType(Type type) {
+    if (typeOperations.isNeverType(type)) {
+      return StaticType.neverType;
+    } else if (typeOperations.isNullType(type)) {
+      return StaticType.nullType;
+    } else if (typeOperations.isNonNullableObject(type)) {
+      return StaticType.nonNullableObject;
+    } else if (typeOperations.isNullableObject(type) ||
+        typeOperations.isDynamic(type)) {
+      return StaticType.nullableObject;
+    }
+
+    StaticType staticType;
+    Type nonNullable = typeOperations.getNonNullable(type);
+    if (typeOperations.isBoolType(nonNullable)) {
+      staticType = _boolStaticType;
+    } else if (typeOperations.isRecordType(nonNullable)) {
+      staticType = new RecordStaticType(typeOperations, this, nonNullable);
+    } else {
+      Type? futureOrTypeArgument =
+          typeOperations.getFutureOrTypeArgument(nonNullable);
+      if (futureOrTypeArgument != null) {
+        StaticType typeArgument = getStaticType(futureOrTypeArgument);
+        StaticType futureType = getStaticType(
+            typeOperations.instantiateFuture(futureOrTypeArgument));
+        staticType = new FutureOrStaticType(
+            typeOperations, this, nonNullable, typeArgument, futureType);
+      } else {
+        EnumClass? enumClass = enumOperations.getEnumClass(nonNullable);
+        if (enumClass != null) {
+          staticType = new EnumStaticType(
+              typeOperations, this, nonNullable, _getEnumInfo(enumClass));
+        } else {
+          Class? sealedClass =
+              _sealedClassOperations.getSealedClass(nonNullable);
+          if (sealedClass != null) {
+            staticType = new SealedClassStaticType(
+                typeOperations,
+                this,
+                nonNullable,
+                this,
+                _sealedClassOperations,
+                _getSealedClassInfo(sealedClass));
+          } else {
+            Type? listType = typeOperations.getListType(nonNullable);
+            if (listType == nonNullable) {
+              staticType =
+                  new ListTypeStaticType(typeOperations, this, nonNullable);
+            } else {
+              staticType =
+                  new TypeBasedStaticType(typeOperations, this, nonNullable);
+            }
+          }
+        }
+      }
+    }
+    if (typeOperations.isNullable(type)) {
+      staticType = staticType.nullable;
+    }
+    return staticType;
+  }
+
+  /// Returns the [StaticType] for the [enumElementValue] declared by
+  /// [enumClass].
+  StaticType getEnumElementStaticType(
+      EnumClass enumClass, EnumElementValue enumElementValue) {
+    return _getEnumInfo(enumClass).getEnumElement(enumElementValue);
+  }
+
+  /// Creates a new unique [StaticType].
+  StaticType getUnknownStaticType() {
+    // The unknown static type should be based on the nullable `Object`, since
+    // even though it _might_ be `null`, using the nullable `Object` here would
+    // mean that it _does_ include `null`, and we need this type to only cover
+    // itself.
+    return getUniqueStaticType<Object>(
+        typeOperations.nonNullableObjectType, new Object(), '?');
+  }
+
+  /// Returns a [StaticType] of the given [type] with the given
+  /// [textualRepresentation] that unique identifies the [uniqueValue].
+  ///
+  /// This is used for constants that are neither bool nor enum values.
+  StaticType getUniqueStaticType<Identity extends Object>(
+      Type type, Identity uniqueValue, String textualRepresentation) {
+    Type nonNullable = typeOperations.getNonNullable(type);
+    StaticType staticType = _uniqueTypeMap[uniqueValue] ??=
+        new ValueStaticType<Type, Identity>(
+            typeOperations,
+            this,
+            nonNullable,
+            new IdentityRestriction<Identity>(uniqueValue),
+            textualRepresentation);
+    if (typeOperations.isNullable(type)) {
+      staticType = staticType.nullable;
+    }
+    return staticType;
+  }
+
+  /// Returns a [StaticType] of the list [type] with the given [identity] .
+  StaticType getListStaticType(Type type, ListTypeIdentity<Type> identity) {
+    Type nonNullable = typeOperations.getNonNullable(type);
+    StaticType staticType = _uniqueTypeMap[identity] ??=
+        new ListPatternStaticType(
+            typeOperations, this, nonNullable, identity, identity.toString());
+    if (typeOperations.isNullable(type)) {
+      staticType = staticType.nullable;
+    }
+    return staticType;
+  }
+
+  /// Returns a [StaticType] of the map [type] with the given [identity] .
+  StaticType getMapStaticType(Type type, MapTypeIdentity<Type> identity) {
+    Type nonNullable = typeOperations.getNonNullable(type);
+    StaticType staticType = _uniqueTypeMap[identity] ??=
+        new MapPatternStaticType(
+            typeOperations, this, nonNullable, identity, identity.toString());
+    if (typeOperations.isNullable(type)) {
+      staticType = staticType.nullable;
+    }
+    return staticType;
+  }
+
+  @override
+  Map<Key, StaticType> getFieldTypes(Type type) {
+    Map<Key, StaticType>? fields = _fieldCache[type];
+    if (fields == null) {
+      _fieldCache[type] = fields = {};
+      for (MapEntry<Key, Type> entry
+          in typeOperations.getFieldTypes(type).entries) {
+        fields[entry.key] = getStaticType(entry.value);
+      }
+    }
+    return fields;
+  }
+
+  @override
+  StaticType? getAdditionalFieldType(Type type, Key key) {
+    if (key is MapKey) {
+      Type? valueType = typeOperations.getMapValueType(type);
+      if (valueType != null) {
+        return getStaticType(valueType);
+      }
+    } else if (key is HeadKey || key is TailKey) {
+      Type? elementType = typeOperations.getListElementType(type);
+      if (elementType != null) {
+        return getStaticType(elementType);
+      }
+    } else if (key is RestKey) {
+      Type? listType = typeOperations.getListType(type);
+      if (listType != null) {
+        return getStaticType(listType);
+      }
+    } else {
+      return getObjectFieldType(key);
+    }
+    return null;
+  }
+
+  @override
+  StaticType? getObjectFieldType(Key key) {
+    return getFieldTypes(typeOperations.nonNullableObjectType)[key];
+  }
+}
+
+/// Mixin for creating [Space]s from [Pattern]s.
+mixin SpaceCreator<Pattern extends Object, Type extends Object> {
+  TypeOperations<Type> get typeOperations;
+
+  ObjectFieldLookup get objectFieldLookup;
+
+  /// Creates a [StaticType] for an unknown type.
+  ///
+  /// This is used when the type of the pattern is unknown or can't be
+  /// represented as a [StaticType]. This type is unique and ensures that it
+  /// is neither matches anything nor is matched by anything.
+  StaticType createUnknownStaticType();
+
+  /// Creates the [StaticType] for [type].
+  StaticType createStaticType(Type type);
+
+  /// Creates the [StaticType] for [type] restricted by the [contextType].
+  /// If [nonNull] is `true`, the created type is non-nullable.
+  StaticType _createStaticTypeWithContext(StaticType contextType, Type type,
+      {required bool nonNull}) {
+    StaticType staticType = createStaticType(type);
+    if (contextType.isSubtypeOf(staticType)) {
+      staticType = contextType;
+    }
+    if (nonNull && staticType is NullableStaticType) {
+      staticType = staticType.underlying;
+    }
+    return staticType;
+  }
+
+  /// Creates the [StaticType] for the list [type] with the given [identity].
+  StaticType createListType(Type type, ListTypeIdentity<Type> identity);
+
+  /// Creates the [StaticType] for the map [type] with the given [identity].
+  StaticType createMapType(Type type, MapTypeIdentity<Type> identity);
+
+  /// Creates the [Space] for [pattern] at the given [path].
+  ///
+  /// The [contextType] is the [StaticType] in which the pattern match is
+  /// performed. This is used to the restrict type of the created [Space] to
+  /// the types allowed by the context. For instance `Object(:var hashCode)` is
+  /// in itself unrestricted and would yield the top space for matching
+  /// `var hashCode`. Using the [contextType] `int`, as given by the type of
+  /// the `Object.hashCode`, the created space is all `int` values rather than
+  /// all values.
+  ///
+  /// If [nonNull] is `true`, the space is implicitly non-nullable.
+  Space dispatchPattern(Path path, StaticType contextType, Pattern pattern,
+      {required bool nonNull});
+
+  /// Creates the root space for [pattern].
+  Space createRootSpace(StaticType contextType, Pattern pattern,
+      {required bool hasGuard}) {
+    if (hasGuard) {
+      return createUnknownSpace(const Path.root());
+    } else {
+      return dispatchPattern(const Path.root(), contextType, pattern,
+          nonNull: false);
+    }
+  }
+
+  /// Creates the [Space] at [path] for a variable pattern of the declared
+  /// [type].
+  ///
+  /// If [nonNull] is `true`, the space is implicitly non-nullable.
+  Space createVariableSpace(Path path, StaticType contextType, Type type,
+      {required bool nonNull}) {
+    StaticType staticType =
+        _createStaticTypeWithContext(contextType, type, nonNull: nonNull);
+    return new Space(path, staticType);
+  }
+
+  /// Creates the [Space] at [path] for an object pattern of the required [type]
+  /// and [fieldPatterns].
+  ///
+  /// If [nonNull] is `true`, the space is implicitly non-nullable.
+  Space createObjectSpace(Path path, StaticType contextType, Type type,
+      Map<String, Pattern> fieldPatterns,
+      {required bool nonNull}) {
+    StaticType staticType =
+        _createStaticTypeWithContext(contextType, type, nonNull: nonNull);
+    Map<Key, Space> fields = <Key, Space>{};
+    for (MapEntry<String, Pattern> entry in fieldPatterns.entries) {
+      Key key = new NameKey(entry.key);
+      StaticType fieldType = staticType.getField(objectFieldLookup, key) ??
+          StaticType.nullableObject;
+      fields[key] = dispatchPattern(path.add(key), fieldType, entry.value,
+          nonNull: false);
+    }
+    return new Space(path, staticType, fields: fields);
+  }
+
+  /// Creates the [Space] at [path] for a record pattern of the required [type],
+  /// [positionalFields], and [namedFields].
+  Space createRecordSpace(Path path, StaticType contextType, Type recordType,
+      List<Pattern> positionalFields, Map<String, Pattern> namedFields) {
+    StaticType staticType =
+        _createStaticTypeWithContext(contextType, recordType, nonNull: true);
+    Map<Key, Space> fields = <Key, Space>{};
+    for (int index = 0; index < positionalFields.length; index++) {
+      Key key = new RecordIndexKey(index);
+      StaticType fieldType = staticType.getField(objectFieldLookup, key) ??
+          StaticType.nullableObject;
+      fields[key] = dispatchPattern(
+          path.add(key), fieldType, positionalFields[index],
+          nonNull: false);
+    }
+    for (MapEntry<String, Pattern> entry in namedFields.entries) {
+      Key key = new RecordNameKey(entry.key);
+      StaticType fieldType = staticType.getField(objectFieldLookup, key) ??
+          StaticType.nullableObject;
+      fields[key] = dispatchPattern(path.add(key), fieldType, entry.value,
+          nonNull: false);
+    }
+    return new Space(path, staticType, fields: fields);
+  }
+
+  /// Creates the [Space] at [path] for a wildcard pattern with the declared
+  /// [type].
+  ///
+  /// If [nonNull] is `true`, the space is implicitly non-nullable.
+  Space createWildcardSpace(Path path, StaticType contextType, Type? type,
+      {required bool nonNull}) {
+    if (type == null) {
+      StaticType staticType = contextType;
+      if (nonNull && staticType is NullableStaticType) {
+        staticType = staticType.underlying;
+      }
+      return new Space(path, staticType);
+    } else {
+      StaticType staticType =
+          _createStaticTypeWithContext(contextType, type, nonNull: nonNull);
+      return new Space(path, staticType);
+    }
+  }
+
+  /// Creates the [Space] at [path] for a relational pattern.
+  Space createRelationalSpace(Path path) {
+    // This pattern do not add to the exhaustiveness coverage.
+    return createUnknownSpace(path);
+  }
+
+  /// Creates the [Space] at [path] for a cast pattern with the given
+  /// [subPattern].
+  ///
+  /// If [nonNull] is `true`, the space is implicitly non-nullable.
+  Space createCastSpace(
+      Path path, StaticType contextType, Type type, Pattern subPattern,
+      {required bool nonNull}) {
+    Space space =
+        dispatchPattern(path, contextType, subPattern, nonNull: nonNull);
+    StaticType castType = createStaticType(type);
+    if (castType.isSubtypeOf(contextType) && contextType.isSealed) {
+      for (StaticType subtype in expandSealedSubtypes(contextType, const {})) {
+        // If [subtype] is a subtype of [castType] it will not throw and must
+        // be handled by [subPattern]. For instance
+        //
+        //    sealed class S {}
+        //    sealed class X extends S {}
+        //    class A extends X {}
+        //    method(S s) => switch (s) {
+        //      A() as X => 0,
+        //    }
+        //
+        // If [castType] is a subtype of [subtype] it might not throw but still
+        // not handle all values of the [subtype].
+        //
+        //    sealed class S {}
+        //    class A extends S {}
+        //    class X extends A {
+        //      int field;
+        //      X(this.field);
+        //    }
+        //    method(S s) => switch (s) {
+        //      X(field: 42) as X => 0,
+        //    }
+        //
+        if (!castType.isSubtypeOf(subtype) && !subtype.isSubtypeOf(castType)) {
+          // Otherwise the cast implicitly handles [subtype].
+          space = space.union(new Space(path, subtype));
+        }
+      }
+    }
+    return space;
+  }
+
+  /// Creates the [Space] at [path] for a null check pattern with the given
+  /// [subPattern].
+  Space createNullCheckSpace(
+      Path path, StaticType contextType, Pattern subPattern) {
+    return dispatchPattern(path, contextType, subPattern, nonNull: true);
+  }
+
+  /// Creates the [Space] at [path] for a null assert pattern with the given
+  /// [subPattern].
+  Space createNullAssertSpace(
+      Path path, StaticType contextType, Pattern subPattern) {
+    Space space = dispatchPattern(path, contextType, subPattern, nonNull: true);
+    return space.union(new Space(path, StaticType.nullType));
+  }
+
+  /// Creates the [Space] at [path] for a logical or pattern with the given
+  /// [left] and [right] subpatterns.
+  ///
+  /// If [nonNull] is `true`, the space is implicitly non-nullable.
+  Space createLogicalOrSpace(
+      Path path, StaticType contextType, Pattern left, Pattern right,
+      {required bool nonNull}) {
+    Space aSpace = dispatchPattern(path, contextType, left, nonNull: nonNull);
+    Space bSpace = dispatchPattern(path, contextType, right, nonNull: nonNull);
+    return aSpace.union(bSpace);
+  }
+
+  /// Creates the [Space] at [path] for a logical and pattern with the given
+  /// [left] and [right] subpatterns.
+  ///
+  /// If [nonNull] is `true`, the space is implicitly non-nullable.
+  Space createLogicalAndSpace(
+      Path path, StaticType contextType, Pattern left, Pattern right,
+      {required bool nonNull}) {
+    Space aSpace = dispatchPattern(path, contextType, left, nonNull: nonNull);
+    Space bSpace = dispatchPattern(path, contextType, right, nonNull: nonNull);
+    return _createSpaceIntersection(path, aSpace, bSpace);
+  }
+
+  /// Creates the [Space] at [path] for a list pattern.
+  Space createListSpace(Path path,
+      {required Type type,
+      required Type elementType,
+      required List<Pattern> headElements,
+      required Pattern? restElement,
+      required List<Pattern> tailElements,
+      required bool hasRest,
+      required bool hasExplicitTypeArgument}) {
+    int headSize = headElements.length;
+    int tailSize = tailElements.length;
+
+    String typeArgumentText;
+    if (hasExplicitTypeArgument) {
+      StringBuffer sb = new StringBuffer();
+      sb.write('<');
+      sb.write(typeOperations.typeToString(elementType));
+      sb.write('>');
+      typeArgumentText = sb.toString();
+    } else {
+      typeArgumentText = '';
+    }
+
+    ListTypeIdentity<Type> identity = new ListTypeIdentity(
+        elementType, typeArgumentText,
+        size: headSize + tailSize, hasRest: hasRest);
+
+    StaticType staticType = createListType(type, identity);
+
+    Map<Key, Space> additionalFields = {};
+    for (int index = 0; index < headSize; index++) {
+      Key key = new HeadKey(index);
+      StaticType fieldType =
+          staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+      additionalFields[key] = dispatchPattern(
+          path.add(key), fieldType, headElements[index],
+          nonNull: false);
+    }
+    if (hasRest) {
+      Key key = new RestKey(headSize, tailSize);
+      StaticType fieldType =
+          staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+      if (restElement != null) {
+        additionalFields[key] = dispatchPattern(
+            path.add(key), fieldType, restElement,
+            nonNull: false);
+      } else {
+        additionalFields[key] = new Space(path.add(key), fieldType);
+      }
+    }
+    for (int index = 0; index < tailSize; index++) {
+      Key key = new TailKey(index);
+      StaticType fieldType =
+          staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+      additionalFields[key] = dispatchPattern(path.add(key), fieldType,
+          tailElements[tailElements.length - index - 1],
+          nonNull: false);
+    }
+    return new Space(path, staticType, additionalFields: additionalFields);
+  }
+
+  /// Creates the [Space] at [path] for a map pattern.
+  Space createMapSpace(Path path,
+      {required Type type,
+      required Type keyType,
+      required Type valueType,
+      required Map<MapKey, Pattern> entries,
+      required bool hasRest,
+      required bool hasExplicitTypeArguments}) {
+    String typeArgumentsText;
+    if (hasExplicitTypeArguments) {
+      StringBuffer sb = new StringBuffer();
+      sb.write('<');
+      sb.write(typeOperations.typeToString(keyType));
+      sb.write(', ');
+      sb.write(typeOperations.typeToString(valueType));
+      sb.write('>');
+      typeArgumentsText = sb.toString();
+    } else {
+      typeArgumentsText = '';
+    }
+
+    MapTypeIdentity<Type> identity = new MapTypeIdentity(
+        keyType, valueType, entries.keys.toSet(), typeArgumentsText,
+        hasRest: hasRest);
+    StaticType staticType = createMapType(type, identity);
+
+    Map<Key, Space> additionalFields = {};
+    for (MapEntry<Key, Pattern> entry in entries.entries) {
+      Key key = entry.key;
+      StaticType fieldType =
+          staticType.getAdditionalField(key) ?? StaticType.nullableObject;
+      additionalFields[key] = dispatchPattern(
+          path.add(key), fieldType, entry.value,
+          nonNull: false);
+    }
+    return new Space(path, staticType, additionalFields: additionalFields);
+  }
+
+  /// Creates the [Space] at [path] for a pattern with unknown space.
+  ///
+  /// This is used when the space of the pattern is unknown or can't be
+  /// represented precisely as a union of [SingleSpace]s. This space is unique
+  /// and ensures that it is neither matches anything nor is matched by
+  /// anything.
+  Space createUnknownSpace(Path path) {
+    return new Space(path, createUnknownStaticType());
+  }
+
+  /// Creates an approximation of the intersection of the single spaces [a] and
+  /// [b].
+  SingleSpace? _createSingleSpaceIntersection(
+      Path path, SingleSpace a, SingleSpace b) {
+    StaticType? type;
+    if (a.type.isSubtypeOf(b.type)) {
+      type = a.type;
+    } else if (b.type.isSubtypeOf(a.type)) {
+      type = b.type;
+    }
+    if (type == null) {
+      return null;
+    }
+    Map<Key, Space> fields = {};
+    for (MapEntry<Key, Space> entry in a.fields.entries) {
+      Key key = entry.key;
+      Space aSpace = entry.value;
+      Space? bSpace = b.fields[key];
+      if (bSpace != null) {
+        fields[key] = _createSpaceIntersection(path.add(key), aSpace, bSpace);
+      } else {
+        fields[key] = aSpace;
+      }
+    }
+    for (MapEntry<Key, Space> entry in b.fields.entries) {
+      Key key = entry.key;
+      fields[key] ??= entry.value;
+    }
+    return new SingleSpace(type, fields: fields);
+  }
+
+  /// Creates an approximation of the intersection of spaces [a] and [b].
+  Space _createSpaceIntersection(Path path, Space a, Space b) {
+    assert(
+        path == a.path, "Unexpected path. Expected $path, actual ${a.path}.");
+    assert(
+        path == b.path, "Unexpected path. Expected $path, actual ${b.path}.");
+    List<SingleSpace> singleSpaces = [];
+    bool hasUnknownSpace = false;
+    for (SingleSpace aSingleSpace in a.singleSpaces) {
+      for (SingleSpace bSingleSpace in b.singleSpaces) {
+        SingleSpace? space =
+            _createSingleSpaceIntersection(path, aSingleSpace, bSingleSpace);
+        if (space != null) {
+          singleSpaces.add(space);
+        } else {
+          hasUnknownSpace = true;
+        }
+      }
+    }
+    if (hasUnknownSpace) {
+      singleSpaces.add(new SingleSpace(createUnknownStaticType()));
+    }
+    return new Space.fromSingleSpaces(path, singleSpaces);
+  }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart
index 9a08e11..4179730 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/space.dart
@@ -1,156 +1,120 @@
-// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2023, 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 'equal.dart';
+import 'key.dart';
+import 'path.dart';
 import 'static_type.dart';
 
-/// The main space for matching types and destructuring.
+/// The main pattern for matching types and destructuring.
 ///
 /// It has a type which determines the type of values it contains. The type may
 /// be [StaticType.nullableObject] to indicate that it doesn't filter by type.
 ///
-/// It may also contain zero or more named fields. The space then only contains
-/// values where the field values are contained by the corresponding field
-/// spaces.
-class ExtractSpace extends Space {
-  /// The type of values the space matches.
+/// It may also contain zero or more named fields. The pattern then only matches
+/// values where the field values are matched by the corresponding field
+/// patterns.
+class SingleSpace {
+  static final SingleSpace empty = new SingleSpace(StaticType.neverType);
+
+  /// The type of values the pattern matches.
   final StaticType type;
 
-  /// Any field subspaces the space matches.
-  final Map<String, Space> fields;
+  /// Any field subpatterns the pattern matches.
+  final Map<Key, Space> fields;
 
-  ExtractSpace._(this.type, [this.fields = const {}]) : super._();
+  /// Additional fields for map/list semantics.
+  final Map<Key, Space> additionalFields;
 
-  /// An [ExtractSpace] with no type and no fields contains all values.
+  SingleSpace(this.type,
+      {this.fields = const {}, this.additionalFields = const {}});
+
   @override
-  bool get isTop => type == StaticType.nullableObject && fields.isEmpty;
+  late final int hashCode = Object.hash(
+      type,
+      Object.hashAllUnordered(fields.keys),
+      Object.hashAllUnordered(fields.values));
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    if (other is! SingleSpace) return false;
+    if (type != other.type) return false;
+    if (fields.length != other.fields.length) return false;
+    if (fields.isNotEmpty) {
+      for (MapEntry<Key, Space> entry in fields.entries) {
+        if (entry.value != other.fields[entry.key]) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
 
   @override
   String toString() {
-    if (isTop) return '()';
-    if (this == Space.empty) return '∅';
-
-    if (type.isRecord) {
-      StringBuffer buffer = new StringBuffer();
-      buffer.write('(');
-      bool first = true;
-      type.fields.forEach((String name, StaticType staticType) {
-        if (!first) buffer.write(', ');
-        // TODO(johnniwinther): Ensure using Dart syntax for positional fields.
-        buffer.write('$name: ${fields[name] ?? staticType}');
-        first = false;
-      });
-
-      buffer.write(')');
-      return buffer.toString();
-    } else {
-      // If there are no fields, just show the type.
-      if (fields.isEmpty) return type.name;
-
-      StringBuffer buffer = new StringBuffer();
-      buffer.write(type.name);
-
-      buffer.write('(');
-      bool first = true;
-
-      fields.forEach((String name, Space space) {
-        if (!first) buffer.write(', ');
-        buffer.write('$name: $space');
-        first = false;
-      });
-
-      buffer.write(')');
-      return buffer.toString();
-    }
+    return type.spaceToText(fields, additionalFields);
   }
 }
 
-// TODO(paulberry, rnystrom): List spaces.
+/// A set of runtime values encoded as a union of [SingleSpace]s.
+///
+/// This is used to support logical-or patterns without having to eagerly
+/// expand the subpatterns in the parent context.
+class Space {
+  /// The path of getters that led from the original matched value to value
+  /// matched by this pattern. Used to generate a human-readable witness.
+  final Path path;
 
-abstract class Space {
-  /// The uninhabited space.
-  static final Space empty = new Space(StaticType.neverType);
+  final List<SingleSpace> singleSpaces;
 
-  /// The space containing everything.
-  static final Space top = new Space(StaticType.nullableObject);
+  /// Create an empty space.
+  Space.empty(this.path) : singleSpaces = [SingleSpace.empty];
 
-  /// The space containing only `null`.
-  static final Space nullSpace = new Space(StaticType.nullType);
+  Space(Path path, StaticType type,
+      {Map<Key, Space> fields = const {},
+      Map<Key, Space> additionalFields = const {}})
+      : this._(path, [
+          new SingleSpace(type,
+              fields: fields, additionalFields: additionalFields)
+        ]);
 
-  factory Space(StaticType type, [Map<String, Space> fields = const {}]) =>
-      new ExtractSpace._(type, fields);
+  Space._(this.path, this.singleSpaces);
 
-  factory Space.union(List<Space> arms) {
-    // Simplify the arms if possible.
-    List<ExtractSpace> allArms = <ExtractSpace>[];
+  factory Space.fromSingleSpaces(Path path, List<SingleSpace> singleSpaces) {
+    Set<SingleSpace> singleSpacesSet = {};
 
-    void addSpace(ExtractSpace space) {
-      // Discard duplicate arms. Duplicates can appear when working through a
-      // series of cases that destructure multiple fields with different types.
-      // Discarding the duplicates isn't necessary for correctness (a union with
-      // redundant arms contains the same set of values), but improves
-      // performance greatly. In the "sealed subtypes large T with all cases"
-      // test, you end up with a union containing 2520 arms, 2488 are
-      // duplicates. With this check, the largest union has only 5 arms.
-      //
-      // This is O(n^2) since we define only equality on spaces, but a real
-      // implementation would likely define hash code too and then simply
-      // create a hash set to merge duplicates in O(n) time.
-      for (Space existing in allArms) {
-        if (equal(existing, space, 'dedupe union')) return;
+    for (SingleSpace singleSpace in singleSpaces) {
+      // Discard empty space.
+      if (singleSpace == SingleSpace.empty) {
+        continue;
       }
 
-      allArms.add(space);
+      singleSpacesSet.add(singleSpace);
     }
 
-    for (Space space in arms) {
-      // Discard empty arms.
-      if (space == empty) continue;
-
-      // Flatten unions. We don't need to flatten recursively since we always
-      // go through this constructor to create unions. A UnionSpace will never
-      // contain UnionSpaces.
-      if (space is UnionSpace) {
-        for (ExtractSpace arm in space.arms) {
-          addSpace(arm);
-        }
-      } else {
-        addSpace(space as ExtractSpace);
+    List<SingleSpace> singleSpacesList = singleSpacesSet.toList();
+    if (singleSpacesSet.isEmpty) {
+      singleSpacesList.add(SingleSpace.empty);
+    } else if (singleSpacesList.length == 2) {
+      if (singleSpacesList[0].type == StaticType.nullType &&
+          singleSpacesList[0].fields.isEmpty &&
+          singleSpacesList[1].fields.isEmpty) {
+        singleSpacesList = [new SingleSpace(singleSpacesList[1].type.nullable)];
+      } else if (singleSpacesList[1].type == StaticType.nullType &&
+          singleSpacesList[1].fields.isEmpty &&
+          singleSpacesList[0].fields.isEmpty) {
+        singleSpacesList = [new SingleSpace(singleSpacesList[0].type.nullable)];
       }
     }
-
-    if (allArms.isEmpty) return empty;
-    if (allArms.length == 1) return allArms.first;
-    if (allArms.length == 2) {
-      if (allArms[0].type == StaticType.nullType &&
-          allArms[0].fields.isEmpty &&
-          allArms[1].fields.isEmpty) {
-        return new Space(allArms[1].type.nullable);
-      } else if (allArms[1].type == StaticType.nullType &&
-          allArms[1].fields.isEmpty &&
-          allArms[0].fields.isEmpty) {
-        return new Space(allArms[0].type.nullable);
-      }
-    }
-    return new UnionSpace._(allArms);
+    return new Space._(path, singleSpacesList);
   }
 
-  Space._();
-
-  /// An untyped record space with no fields matches all values and thus isn't
-  /// very useful.
-  bool get isTop => false;
-}
-
-/// A union of spaces. The space A|B contains all of the values of A and B.
-class UnionSpace extends Space {
-  final List<ExtractSpace> arms;
-
-  UnionSpace._(this.arms) : super._() {
-    assert(arms.length > 1);
+  Space union(Space other) {
+    return new Space.fromSingleSpaces(
+        path, [...singleSpaces, ...other.singleSpaces]);
   }
 
   @override
-  String toString() => arms.join('|');
+  String toString() => singleSpaces.join('|');
 }
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart
index d2ea571..3280423 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart
@@ -2,7 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// TODO(paulberry,rnystrom): Generics.
+import 'key.dart';
+import 'space.dart';
+import 'witness.dart';
 
 /// A static type in the type system.
 abstract class StaticType {
@@ -22,7 +24,21 @@
   /// The static types of the fields this type exposes for record destructuring.
   ///
   /// Includes inherited fields.
-  Map<String, StaticType> get fields;
+  Map<Key, StaticType> get fields;
+
+  /// Returns the static type for the [name] in this static type, or `null` if
+  /// no such key exists.
+  ///
+  /// This is used to support implicit on the constant [StaticType]s
+  /// [nullableObject], [nonNullableObject], [nullType] and [neverType].
+  StaticType? getField(ObjectFieldLookup fieldLookup, Key key);
+
+  /// Returns the static type for the [key] in this static type, or `null` if
+  /// no such key exists.
+  ///
+  /// This is used to model keys in map patterns, and indices and ranges in list
+  /// patterns.
+  StaticType? getAdditionalField(Key key);
 
   /// Returns `true` if this static type is a subtype of [other], taking the
   /// nullability and subtyping relation into account.
@@ -50,7 +66,7 @@
 
   /// Returns `true` if this is a record type.
   ///
-  /// This is only used for print the type as part of a [Space].
+  /// This is only used for print the type as part of a [Witness].
   bool get isRecord;
 
   /// Returns the name of this static type.
@@ -61,8 +77,30 @@
   /// Returns the nullable static type corresponding to this type.
   StaticType get nullable;
 
+  /// Returns the non-nullable static type corresponding to this type.
+  StaticType get nonNullable;
+
   /// The immediate subtypes of this type.
-  Iterable<StaticType> get subtypes;
+  ///
+  /// The [keysOfInterest] of interest are the keys used in one of the case
+  /// rows. This is used to select how a `List` type should be divided into
+  /// subtypes that should be used for testing the exhaustiveness of a list.
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest);
+
+  /// Returns a textual representation of a single space consisting of this
+  /// type and the provided [fields] and [additionalFields].
+  String spaceToText(
+      Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields);
+
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields);
+}
+
+mixin _ObjectFieldMixin on _BaseStaticType {
+  @override
+  StaticType? getField(ObjectFieldLookup fieldLookup, Key key) {
+    return fields[key] ?? fieldLookup.getObjectFieldType(key);
+  }
 }
 
 abstract class _BaseStaticType implements StaticType {
@@ -72,16 +110,76 @@
   bool get isRecord => false;
 
   @override
-  Map<String, StaticType> get fields => const {};
+  Map<Key, StaticType> get fields => const {};
 
   @override
-  Iterable<StaticType> get subtypes => const [];
+  StaticType? getField(ObjectFieldLookup fieldLookup, Key name) {
+    return fields[name];
+  }
+
+  @override
+  StaticType? getAdditionalField(Key key) => null;
+
+  @override
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) => const [];
+
+  @override
+  String spaceToText(
+      Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+    assert(additionalSpaceFields.isEmpty,
+        "Additional fields not supported in ${runtimeType}.");
+    if (this == StaticType.nullableObject && spaceFields.isEmpty) return '()';
+    if (this == StaticType.neverType && spaceFields.isEmpty) return '∅';
+
+    // If there are no fields, just show the type.
+    if (spaceFields.isEmpty) return name;
+
+    StringBuffer buffer = new StringBuffer();
+    buffer.write(name);
+
+    buffer.write('(');
+    bool first = true;
+
+    spaceFields.forEach((Key key, Space space) {
+      if (!first) buffer.write(', ');
+      buffer.write('${key.name}: $space');
+      first = false;
+    });
+
+    buffer.write(')');
+    return buffer.toString();
+  }
+
+  @override
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields) {
+    if (this == StaticType.nullableObject && witnessFields.isEmpty) {
+      buffer.write('_');
+    } else if (this == StaticType.nullType && witnessFields.isEmpty) {
+      buffer.write('null');
+    } else {
+      buffer.write(name);
+      buffer.write('(');
+      if (witnessFields.isNotEmpty) {
+        String comma = '';
+        for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+          buffer.write(comma);
+          comma = ', ';
+
+          buffer.write(entry.key.name);
+          buffer.write(': ');
+          entry.value.witnessToText(buffer);
+        }
+      }
+      buffer.write(')');
+    }
+  }
 
   @override
   String toString() => name;
 }
 
-class _NonNullableObject extends _BaseStaticType {
+class _NonNullableObject extends _BaseStaticType with _ObjectFieldMixin {
   const _NonNullableObject();
 
   @override
@@ -98,9 +196,12 @@
 
   @override
   StaticType get nullable => StaticType.nullableObject;
+
+  @override
+  StaticType get nonNullable => this;
 }
 
-class _NeverType extends _BaseStaticType {
+class _NeverType extends _BaseStaticType with _ObjectFieldMixin {
   const _NeverType();
 
   @override
@@ -117,9 +218,12 @@
 
   @override
   StaticType get nullable => StaticType.nullType;
+
+  @override
+  StaticType get nonNullable => this;
 }
 
-class _NullType extends NullableStaticType {
+class _NullType extends NullableStaticType with _ObjectFieldMixin {
   const _NullType(super.underlying);
 
   @override
@@ -129,7 +233,7 @@
   }
 
   @override
-  Iterable<StaticType> get subtypes {
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) {
     // Avoid splitting into [nullType] and [neverType].
     return const [];
   }
@@ -138,7 +242,7 @@
   String get name => 'Null';
 }
 
-class NullableStaticType extends _BaseStaticType {
+class NullableStaticType extends _BaseStaticType with _ObjectFieldMixin {
   final StaticType underlying;
 
   const NullableStaticType(this.underlying);
@@ -147,7 +251,8 @@
   bool get isSealed => true;
 
   @override
-  Iterable<StaticType> get subtypes => [underlying, StaticType.nullType];
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+      [underlying, StaticType.nullType];
 
   @override
   bool isSubtypeOf(StaticType other) {
@@ -163,6 +268,9 @@
   StaticType get nullable => this;
 
   @override
+  StaticType get nonNullable => underlying;
+
+  @override
   int get hashCode => underlying.hashCode * 11;
 
   @override
@@ -177,6 +285,9 @@
   late final StaticType nullable = new NullableStaticType(this);
 
   @override
+  StaticType get nonNullable => this;
+
+  @override
   bool isSubtypeOf(StaticType other) {
     if (this == other) return true;
 
@@ -192,7 +303,15 @@
       return isSubtypeOf(other.underlying);
     }
 
-    return isSubtypeOfInternal(other);
+    if (isSubtypeOfInternal(other)) {
+      return true;
+    }
+
+    if (other is WrappedStaticType) {
+      return isSubtypeOf(other.wrappedType) && isSubtypeOf(other.impliedType);
+    }
+
+    return false;
   }
 
   bool isSubtypeOfInternal(StaticType other);
@@ -200,3 +319,58 @@
   @override
   String toString() => name;
 }
+
+/// Static type the behaves like [wrappedType] but is also a subtype of
+/// [impliedType].
+class WrappedStaticType extends NonNullableStaticType {
+  final StaticType wrappedType;
+  final StaticType impliedType;
+
+  WrappedStaticType(this.wrappedType, this.impliedType);
+
+  @override
+  Map<Key, StaticType> get fields => wrappedType.fields;
+
+  @override
+  bool get isRecord => wrappedType.isRecord;
+
+  @override
+  bool get isSealed => wrappedType.isSealed;
+
+  @override
+  String get name => wrappedType.name;
+
+  @override
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) => wrappedType
+      .getSubtypes(keysOfInterest)
+      .map((e) => new WrappedStaticType(e, impliedType));
+
+  @override
+  bool isSubtypeOfInternal(StaticType other) {
+    return wrappedType.isSubtypeOf(other) || impliedType.isSubtypeOf(other);
+  }
+
+  @override
+  int get hashCode => Object.hash(wrappedType, impliedType);
+
+  @override
+  bool operator ==(other) {
+    if (identical(this, other)) return true;
+    return other is WrappedStaticType &&
+        wrappedType == other.wrappedType &&
+        impliedType == other.impliedType;
+  }
+
+  @override
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields) {
+    return wrappedType.witnessToText(buffer, witness, witnessFields);
+  }
+}
+
+/// Interface for accessing the members defined on `Object`.
+abstract class ObjectFieldLookup {
+  /// Returns the [StaticType] for the member with the given [key] defined on
+  /// `Object`, or `null` none exists.
+  StaticType? getObjectFieldType(Key key);
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/static_types.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/static_types.dart
deleted file mode 100644
index e637152..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/static_types.dart
+++ /dev/null
@@ -1,513 +0,0 @@
-// Copyright (c) 2022, 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:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
-
-/// Interface implemented by analyze/CFE to support type operations need for the
-/// shared [StaticType]s.
-abstract class TypeOperations<Type extends Object> {
-  /// Returns the type for `Object`.
-  Type get nullableObjectType;
-
-  /// Returns `true` if [s] is a subtype of [t].
-  bool isSubtypeOf(Type s, Type t);
-
-  /// Returns `true` if [type] is a potentially nullable type.
-  bool isNullable(Type type);
-
-  /// Returns the non-nullable type corresponding to [type]. For instance
-  /// `Foo` for `Foo?`. If [type] is already non-nullable, it itself is
-  /// returned.
-  Type getNonNullable(Type type);
-
-  /// Returns `true` if [type] is the `Null` type.
-  bool isNullType(Type type);
-
-  /// Returns `true` if [type] is the `Never` type.
-  bool isNeverType(Type type);
-
-  /// Returns `true` if [type] is the `Object?` type.
-  bool isNullableObject(Type type);
-
-  /// Returns `true` if [type] is the `Object` type.
-  bool isNonNullableObject(Type type);
-
-  /// Returns `true` if [type] is the `bool` type.
-  bool isBoolType(Type type);
-
-  /// Returns the `bool` type.
-  Type get boolType;
-
-  /// Returns `true` if [type] is a record type.
-  bool isRecordType(Type type);
-
-  /// Returns a map of the field names and corresponding types available on
-  /// [type]. For an interface type, these are the fields and getters, and for
-  /// record types these are the record fields.
-  Map<String, Type> getFieldTypes(Type type);
-
-  /// Returns a human-readable representation of the [type].
-  String typeToString(Type type);
-}
-
-/// Interface implemented by analyzer/CFE to support [StaticType]s for enums.
-abstract class EnumOperations<Type extends Object, EnumClass extends Object,
-    EnumElement extends Object, EnumElementValue extends Object> {
-  /// Returns the enum class declaration for the [type] or `null` if
-  /// [type] is not an enum type.
-  EnumClass? getEnumClass(Type type);
-
-  /// Returns the enum elements defined by [enumClass].
-  Iterable<EnumElement> getEnumElements(EnumClass enumClass);
-
-  /// Returns the value defined by the [enumElement]. The encoding is specific
-  /// the implementation of this interface but must ensure constant value
-  /// identity.
-  EnumElementValue getEnumElementValue(EnumElement enumElement);
-
-  /// Returns the declared name of the [enumElement].
-  String getEnumElementName(EnumElement enumElement);
-
-  /// Returns the static type of the [enumElement].
-  Type getEnumElementType(EnumElement enumElement);
-}
-
-/// Interface implemented by analyzer/CFE to support [StaticType]s for sealed
-/// classes.
-abstract class SealedClassOperations<Type extends Object,
-    Class extends Object> {
-  /// Returns the sealed class declaration for [type] or `null` if [type] is not
-  /// a sealed class type.
-  Class? getSealedClass(Type type);
-
-  /// Returns the direct subclasses of [sealedClass] that either extend,
-  /// implement or mix it in.
-  List<Class> getDirectSubclasses(Class sealedClass);
-
-  /// Returns the instance of [subClass] that implements [sealedClassType].
-  ///
-  /// `null` might be returned if [subClass] cannot implement [sealedClassType].
-  /// For instance
-  ///
-  ///     sealed class A<T> {}
-  ///     class B<T> extends A<T> {}
-  ///     class C extends A<int> {}
-  ///
-  /// here `C` has no implementation of `A<String>`.
-  ///
-  /// It is assumed that `TypeOperations.isSealedClass` is `true` for
-  /// [sealedClassType] and that [subClass] is in `getDirectSubclasses` for
-  /// `getSealedClass` of [sealedClassType].
-  ///
-  // TODO(johnniwinther): What should this return for generic types?
-  Type? getSubclassAsInstanceOf(Class subClass, Type sealedClassType);
-}
-
-/// Interface for looking up fields and their corresponding [StaticType]s of
-/// a given type.
-abstract class FieldLookup<Type extends Object> {
-  /// Returns a map of the field names and corresponding [StaticType]s available
-  /// on [type]. For an interface type, these are the fields and getters, and
-  /// for record types these are the record fields.
-  Map<String, StaticType> getFieldTypes(Type type);
-}
-
-/// Cache used for computing [StaticType]s used for exhaustiveness checking.
-///
-/// This implementation is shared between analyzer and CFE, and implemented
-/// using the analyzer/CFE implementations of [TypeOperations],
-/// [EnumOperations], and [SealedClassOperations].
-class ExhaustivenessCache<
-    Type extends Object,
-    Class extends Object,
-    EnumClass extends Object,
-    EnumElement extends Object,
-    EnumElementValue extends Object> implements FieldLookup<Type> {
-  final TypeOperations<Type> _typeOperations;
-  final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
-      enumOperations;
-  final SealedClassOperations<Type, Class> _sealedClassOperations;
-
-  /// Cache for [EnumInfo] for enum classes.
-  Map<EnumClass, EnumInfo<Type, EnumClass, EnumElement, EnumElementValue>>
-      _enumInfo = {};
-
-  /// Cache for [SealedClassInfo] for sealed classes.
-  Map<Class, SealedClassInfo<Type, Class>> _sealedClassInfo = {};
-
-  /// Cache for [UniqueStaticType]s.
-  Map<Object, StaticType> _uniqueTypeMap = {};
-
-  /// Cache for the [StaticType] for `bool`.
-  late BoolStaticType _boolStaticType =
-      new BoolStaticType(_typeOperations, this, _typeOperations.boolType);
-
-  /// Cache for [StaticType]s for fields available on a [Type].
-  Map<Type, Map<String, StaticType>> _fieldCache = {};
-
-  ExhaustivenessCache(
-      this._typeOperations, this.enumOperations, this._sealedClassOperations);
-
-  /// Returns the [EnumInfo] for [enumClass].
-  EnumInfo<Type, EnumClass, EnumElement, EnumElementValue> _getEnumInfo(
-      EnumClass enumClass) {
-    return _enumInfo[enumClass] ??=
-        new EnumInfo(_typeOperations, this, enumOperations, enumClass);
-  }
-
-  /// Returns the [SealedClassInfo] for [sealedClass].
-  SealedClassInfo<Type, Class> _getSealedClassInfo(Class sealedClass) {
-    return _sealedClassInfo[sealedClass] ??=
-        new SealedClassInfo(_sealedClassOperations, sealedClass);
-  }
-
-  /// Returns the [StaticType] for the boolean [value].
-  StaticType getBoolValueStaticType(bool value) {
-    return value ? _boolStaticType.trueType : _boolStaticType.falseType;
-  }
-
-  /// Returns the [StaticType] for [type].
-  StaticType getStaticType(Type type) {
-    if (_typeOperations.isNeverType(type)) {
-      return StaticType.neverType;
-    } else if (_typeOperations.isNullType(type)) {
-      return StaticType.nullType;
-    } else if (_typeOperations.isNonNullableObject(type)) {
-      return StaticType.nonNullableObject;
-    } else if (_typeOperations.isNullableObject(type)) {
-      return StaticType.nullableObject;
-    }
-
-    StaticType staticType;
-    Type nonNullable = _typeOperations.getNonNullable(type);
-    if (_typeOperations.isBoolType(nonNullable)) {
-      staticType = _boolStaticType;
-    } else if (_typeOperations.isRecordType(nonNullable)) {
-      staticType = new RecordStaticType(_typeOperations, this, nonNullable);
-    } else {
-      EnumClass? enumClass = enumOperations.getEnumClass(nonNullable);
-      if (enumClass != null) {
-        staticType = new EnumStaticType(
-            _typeOperations, this, nonNullable, _getEnumInfo(enumClass));
-      } else {
-        Class? sealedClass = _sealedClassOperations.getSealedClass(nonNullable);
-        if (sealedClass != null) {
-          staticType = new SealedClassStaticType(
-              _typeOperations,
-              this,
-              nonNullable,
-              this,
-              _sealedClassOperations,
-              _getSealedClassInfo(sealedClass));
-        } else {
-          staticType =
-              new TypeBasedStaticType(_typeOperations, this, nonNullable);
-        }
-      }
-    }
-    if (_typeOperations.isNullable(type)) {
-      staticType = staticType.nullable;
-    }
-    return staticType;
-  }
-
-  /// Returns the [StaticType] for the [enumElementValue] declared by
-  /// [enumClass].
-  StaticType getEnumElementStaticType(
-      EnumClass enumClass, EnumElementValue enumElementValue) {
-    return _getEnumInfo(enumClass).getEnumElement(enumElementValue);
-  }
-
-  /// Creates a new unique [StaticType].
-  StaticType getUnknownStaticType() {
-    return getUniqueStaticType(
-        _typeOperations.nullableObjectType, new Object(), '?');
-  }
-
-  /// Returns a [StaticType] of the given [type] with the given
-  /// [textualRepresentation] that unique identifies the [uniqueValue].
-  ///
-  /// This is used for constants that are neither bool nor enum values.
-  StaticType getUniqueStaticType(
-      Type type, Object uniqueValue, String textualRepresentation) {
-    Type nonNullable = _typeOperations.getNonNullable(type);
-    StaticType staticType = _uniqueTypeMap[uniqueValue] ??=
-        new UniqueStaticType(_typeOperations, this, nonNullable, uniqueValue,
-            textualRepresentation);
-    if (_typeOperations.isNullable(type)) {
-      staticType = staticType.nullable;
-    }
-    return staticType;
-  }
-
-  @override
-  Map<String, StaticType> getFieldTypes(Type type) {
-    Map<String, StaticType>? fields = _fieldCache[type];
-    if (fields == null) {
-      _fieldCache[type] = fields = {};
-      for (MapEntry<String, Type> entry
-          in _typeOperations.getFieldTypes(type).entries) {
-        fields[entry.key] = getStaticType(entry.value);
-      }
-    }
-    return fields;
-  }
-}
-
-/// [EnumInfo] stores information to compute the static type for and the type
-/// of and enum class and its enum elements.
-class EnumInfo<Type extends Object, EnumClass extends Object,
-    EnumElement extends Object, EnumElementValue extends Object> {
-  final TypeOperations<Type> _typeOperations;
-  final FieldLookup<Type> _fieldLookup;
-  final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
-      _enumOperations;
-  final EnumClass _enumClass;
-  Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>?
-      _enumElements;
-
-  EnumInfo(this._typeOperations, this._fieldLookup, this._enumOperations,
-      this._enumClass);
-
-  /// Returns a map of the enum elements and their corresponding [StaticType]s
-  /// declared by [_enumClass].
-  Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
-      get enumElements => _enumElements ??= _createEnumElements();
-
-  /// Returns the [StaticType] corresponding to [enumElementValue].
-  EnumElementStaticType<Type, EnumElement> getEnumElement(
-      EnumElementValue enumElementValue) {
-    return enumElements[enumElementValue]!;
-  }
-
-  Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
-      _createEnumElements() {
-    Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>> elements =
-        {};
-    for (EnumElement element in _enumOperations.getEnumElements(_enumClass)) {
-      EnumElementValue value = _enumOperations.getEnumElementValue(element);
-      elements[value] = new EnumElementStaticType<Type, EnumElement>(
-          _typeOperations,
-          _fieldLookup,
-          _enumOperations.getEnumElementType(element),
-          element,
-          _enumOperations.getEnumElementName(element));
-    }
-    return elements;
-  }
-}
-
-/// [SealedClassInfo] stores information to compute the static type for a
-/// sealed class.
-class SealedClassInfo<Type extends Object, Class extends Object> {
-  final SealedClassOperations<Type, Class> _sealedClassOperations;
-  final Class _sealedClass;
-  List<Class>? _subClasses;
-
-  SealedClassInfo(this._sealedClassOperations, this._sealedClass);
-
-  /// Returns the classes that directly extends, implements or mix in
-  /// [_sealedClass].
-  Iterable<Class> get subClasses =>
-      _subClasses ??= _sealedClassOperations.getDirectSubclasses(_sealedClass);
-}
-
-/// [StaticType] based on a non-nullable [Type].
-///
-/// All [StaticType] implementation in this library are based on [Type] through
-/// this class. Additionally, the `static_type.dart` library has fixed
-/// [StaticType] implementations for `Object`, `Null`, `Never` and nullable
-/// types.
-class TypeBasedStaticType<Type extends Object> extends NonNullableStaticType {
-  final TypeOperations<Type> _typeOperations;
-  final FieldLookup<Type> _fieldLookup;
-  final Type _type;
-
-  TypeBasedStaticType(this._typeOperations, this._fieldLookup, this._type);
-
-  @override
-  Map<String, StaticType> get fields => _fieldLookup.getFieldTypes(_type);
-
-  /// Returns a non-null value for static types that are unique subtypes of
-  /// the [_type]. For instance individual elements of an enum.
-  Object? get identity => null;
-
-  @override
-  bool isSubtypeOfInternal(StaticType other) {
-    return other is TypeBasedStaticType<Type> &&
-        (other.identity == null || identical(identity, other.identity)) &&
-        _typeOperations.isSubtypeOf(_type, other._type);
-  }
-
-  @override
-  bool get isSealed => false;
-
-  @override
-  String get name => _typeOperations.typeToString(_type);
-
-  @override
-  int get hashCode => Object.hash(_type, identity);
-
-  @override
-  bool operator ==(other) {
-    if (identical(this, other)) return true;
-    return other is TypeBasedStaticType<Type> &&
-        _type == other._type &&
-        identity == other.identity;
-  }
-
-  Type get typeForTesting => _type;
-}
-
-/// [StaticType] for an instantiation of an enum that support access to the
-/// enum values that populate its type through the [subtypes] property.
-class EnumStaticType<Type extends Object, EnumElement extends Object>
-    extends TypeBasedStaticType<Type> {
-  final EnumInfo<Type, Object, EnumElement, Object> _enumInfo;
-  List<EnumElementStaticType<Type, EnumElement>>? _enumElements;
-
-  EnumStaticType(
-      super.typeOperations, super.fieldLookup, super.type, this._enumInfo);
-
-  @override
-  bool get isSealed => true;
-
-  @override
-  Iterable<StaticType> get subtypes => enumElements;
-
-  List<EnumElementStaticType<Type, EnumElement>> get enumElements =>
-      _enumElements ??= _createEnumElements();
-
-  List<EnumElementStaticType<Type, EnumElement>> _createEnumElements() {
-    List<EnumElementStaticType<Type, EnumElement>> elements = [];
-    for (EnumElementStaticType<Type, EnumElement> enumElement
-        in _enumInfo.enumElements.values) {
-      if (_typeOperations.isSubtypeOf(enumElement._type, _type)) {
-        elements.add(enumElement);
-      }
-    }
-    return elements;
-  }
-}
-
-/// [StaticType] for a single enum element.
-///
-/// In the [StaticType] model, individual enum elements are represented as
-/// unique subtypes of the enum type, modelled using [EnumStaticType].
-class EnumElementStaticType<Type extends Object, EnumElement extends Object>
-    extends TypeBasedStaticType<Type> {
-  final EnumElement enumElement;
-
-  @override
-  final String name;
-
-  EnumElementStaticType(super.typeOperations, super.fieldLookup, super.type,
-      this.enumElement, this.name);
-
-  @override
-  Object? get identity => enumElement;
-}
-
-/// [StaticType] for a sealed class type.
-class SealedClassStaticType<Type extends Object, Class extends Object>
-    extends TypeBasedStaticType<Type> {
-  final ExhaustivenessCache<Type, dynamic, dynamic, dynamic, Class> _cache;
-  final SealedClassOperations<Type, Class> _sealedClassOperations;
-  final SealedClassInfo<Type, Class> _sealedInfo;
-  Iterable<StaticType>? _subtypes;
-
-  SealedClassStaticType(super.typeOperations, super.fieldLookup, super.type,
-      this._cache, this._sealedClassOperations, this._sealedInfo);
-
-  @override
-  bool get isSealed => true;
-
-  @override
-  Iterable<StaticType> get subtypes => _subtypes ??= _createSubtypes();
-
-  List<StaticType> _createSubtypes() {
-    List<StaticType> subtypes = [];
-    for (Class subClass in _sealedInfo.subClasses) {
-      Type? subtype =
-          _sealedClassOperations.getSubclassAsInstanceOf(subClass, _type);
-      if (subtype != null) {
-        assert(_typeOperations.isSubtypeOf(subtype, _type));
-        subtypes.add(_cache.getStaticType(subtype));
-      }
-    }
-    return subtypes;
-  }
-}
-
-/// [StaticType] for an object uniquely defined by its [identity].
-class UniqueStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
-  @override
-  final Object identity;
-
-  @override
-  final String name;
-
-  UniqueStaticType(super.typeOperations, super.fieldLookup, super.type,
-      this.identity, this.name);
-}
-
-/// [StaticType] for the `bool` type.
-class BoolStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
-  BoolStaticType(super.typeOperations, super.fieldLookup, super.type);
-
-  @override
-  bool get isSealed => true;
-
-  late StaticType trueType =
-      new UniqueStaticType(_typeOperations, _fieldLookup, _type, true, 'true');
-
-  late StaticType falseType = new UniqueStaticType(
-      _typeOperations, _fieldLookup, _type, false, 'false');
-
-  @override
-  Iterable<StaticType> get subtypes => [trueType, falseType];
-}
-
-/// [StaticType] for a record type.
-///
-/// This models that type aspect of the record using only the structure of the
-/// record type. This means that the type for `(Object, String)` and
-/// `(String, int)` will be subtypes of each other.
-///
-/// This is necessary to avoid invalid conclusions on the disjointness of
-/// spaces base on the their types. For instance in
-///
-///     method((String, Object) o) {
-///       if (o case (Object _, String s)) {}
-///     }
-///
-/// the case is not empty even though `(String, Object)` and `(Object, String)`
-/// are not related type-wise.
-///
-/// Not that the fields of the record types _are_ using the type, so that
-/// the `$1` field of `(String, Object)` is known to contain only `String`s.
-class RecordStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
-  RecordStaticType(super.typeOperations, super.fieldLookup, super.type);
-
-  @override
-  bool get isRecord => true;
-
-  @override
-  bool isSubtypeOfInternal(StaticType other) {
-    if (other is! RecordStaticType<Type>) {
-      return false;
-    }
-    assert(identity == null);
-    if (fields.length != other.fields.length) {
-      return false;
-    }
-    for (MapEntry<String, StaticType> field in fields.entries) {
-      StaticType? type = other.fields[field.key];
-      if (type == null) {
-        return false;
-      }
-    }
-    return true;
-  }
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/subtract.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/subtract.dart
deleted file mode 100644
index 97706e1..0000000
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/subtract.dart
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright (c) 2022, 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 'intersect_empty.dart';
-import 'profile.dart' as profile;
-import 'space.dart';
-import 'static_type.dart';
-
-/// Recursively replaces [left] with a union of its sealed subtypes as long as
-/// doing so enables it to more precisely match against [right].
-List<StaticType> expandType(StaticType left, StaticType right) {
-  // If [left] is nullable and right is null or non-nullable, then expand the
-  // nullable type.
-  if (left is NullableStaticType &&
-      (right == StaticType.nullType || right is! NullableStaticType)) {
-    return [...expandType(left.underlying, right), StaticType.nullType];
-  }
-
-  // If [right] is nullable, then expand using its underlying type.
-  if (right is NullableStaticType) {
-    return expandType(left, right.underlying);
-  }
-
-  // If [left] is a sealed supertype and [right] is in its subtype hierarchy,
-  // then expand out the subtypes (recursively) to more precisely match [right].
-  if (left.isSealed && left != right && right.isSubtypeOf(left)) {
-    return {
-      for (StaticType subtype in left.subtypes) ...expandType(subtype, right),
-    }.toList();
-  }
-
-  if (left == StaticType.neverType) {
-    return const [];
-  }
-  return [left];
-}
-
-/// Returns a new [Space] that contains all of the values of [left] that are
-/// not also in [right].
-Space subtract(Space left, Space right) {
-  profile.count('subtract');
-
-  // Subtracting from empty is still empty.
-  if (left == Space.empty) return Space.empty;
-
-  // Subtracting nothing leaves it unchanged.
-  if (right == Space.empty) return left;
-
-  // Distribute a union on the left.
-  // A|B - x => A-x | B-x
-  if (left is UnionSpace) {
-    return new Space.union(
-        left.arms.map((arm) => subtract(arm, right)).toList());
-  }
-
-  // Distribute a union on the right.
-  // x - A|B => x - A - B
-  if (right is UnionSpace) {
-    Space result = left;
-    for (Space arm in right.arms) {
-      result = subtract(result, arm);
-    }
-    return result;
-  }
-
-  // Otherwise, it must be two extract spaces.
-  return _subtractExtract(left as ExtractSpace, right as ExtractSpace);
-}
-
-/// Returns `true` if every field in [leftFields] is covered by the
-/// corresponding field in [rightFields].
-bool _isLeftSubspace(StaticType leftType, List<String> fieldNames,
-    Map<String, Space> leftFields, Map<String, Space> rightFields) {
-  for (String name in fieldNames) {
-    if (subtract(leftFields[name]!, rightFields[name]!) != Space.empty) {
-      return false;
-    }
-  }
-
-  // If we get here, every field covered.
-  return true;
-}
-
-/// Subtract [right] from [left].
-Space _subtractExtract(ExtractSpace left, ExtractSpace right) {
-  List<String> fieldNames =
-      {...left.fields.keys, ...right.fields.keys}.toList();
-
-  List<Space> spaces = <Space>[];
-
-  // If the left type is in a sealed hierarchy, expanding it to its subtypes
-  // might let us calculate the subtraction more precisely.
-  List<StaticType> subtypes = expandType(left.type, right.type);
-  for (StaticType subtype in subtypes) {
-    spaces.addAll(_subtractExtractAtType(subtype, left, right, fieldNames));
-  }
-
-  return new Space.union(spaces);
-}
-
-/// Subtract [right] from [left], but using [type] for left's type, which may
-/// be a more specific subtype of [left]'s own type is a sealed supertype.
-List<Space> _subtractExtractAtType(StaticType type, ExtractSpace left,
-    ExtractSpace right, List<String> fieldNames) {
-  // If the right type doesn't cover the left (even after expanding sealed
-  // types), then we can't do anything with the fields since they may not
-  // even come into play for all values. Subtract nothing from this subtype
-  // and keep all of the current fields.
-  if (!type.isSubtypeOf(right.type)) return [new Space(type, left.fields)];
-
-  // Infer any fields that appear in one space and not the other.
-  Map<String, Space> leftFields = <String, Space>{};
-  Map<String, Space> rightFields = <String, Space>{};
-  for (String name in fieldNames) {
-    // If the right space matches on a field that the left doesn't have, infer
-    // it from the static type of the field. That contains the same set of
-    // values as having no field at all.
-    assert(type.fields.containsKey(name), "Field '$name' not found in $type.");
-    leftFields[name] = left.fields[name] ?? new Space(type.fields[name]!);
-
-    // If the left matches on a field that the right doesn't have, infer top
-    // for the right field since the right will accept any of left's values for
-    // that field.
-    rightFields[name] = right.fields[name] ?? Space.top;
-  }
-
-  // If any pair of fields have no overlapping values, then no overall value
-  // that matches the left space will also match the right space. So the right
-  // space doesn't subtract anything and we keep the left space as-is.
-  for (String name in fieldNames) {
-    if (spacesHaveEmptyIntersection(leftFields[name]!, rightFields[name]!)) {
-      return [new Space(type, left.fields)];
-    }
-  }
-
-  // If all the right's fields strictly cover all of the left's, then the
-  // right completely subtracts this type and nothing remains.
-  if (_isLeftSubspace(type, fieldNames, leftFields, rightFields)) {
-    return const [];
-  }
-
-  // The right side is a supertype but its fields don't totally cover, so
-  // handle each of them individually.
-
-  // Walk the fields and see which ones are modified by the right-hand fields.
-  Map<String, Space> fixed = <String, Space>{};
-  Map<String, Space> changedDifference = <String, Space>{};
-  for (String name in fieldNames) {
-    Space difference = subtract(leftFields[name]!, rightFields[name]!);
-    if (difference == Space.empty) {
-      // The right field accepts all the values that the left field accepts, so
-      // keep the left field as it is.
-      fixed[name] = leftFields[name]!;
-    } else if (difference.isTop) {
-      // If the resulting field matches everything, simply discard it since
-      // it's equivalent to omitting the field.
-    } else {
-      changedDifference[name] = difference;
-    }
-  }
-
-  // If no fields are affected by the subtraction, just return a single arm
-  // with all of the fields.
-  if (changedDifference.isEmpty) return [new Space(type, fixed)];
-
-  // For each field whose `left - right` is different, include an arm that
-  // includes that one difference.
-  List<String> changedFields = changedDifference.keys.toList();
-  List<Space> spaces = <Space>[];
-  for (int i = 0; i < changedFields.length; i++) {
-    Map<String, Space> fields = {...fixed};
-
-    for (int j = 0; j < changedFields.length; j++) {
-      String name = changedFields[j];
-      if (i == j) {
-        fields[name] = changedDifference[name]!;
-      } else {
-        fields[name] = leftFields[name]!;
-      }
-    }
-
-    spaces.add(new Space(type, fields));
-  }
-
-  return spaces;
-}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart
index 2bf6915..9e3bdb8 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/test_helper.dart
@@ -2,8 +2,8 @@
 // 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:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
-
+import 'exhaustive.dart';
+import 'key.dart';
 import 'space.dart';
 import 'static_type.dart';
 
@@ -14,30 +14,36 @@
   static const String scrutineeFields = 'fields';
   static const String space = 'space';
   static const String subtypes = 'subtypes';
-  static const String remaining = 'remaining';
+  static const String expandedSubtypes = 'expandedSubtypes';
 }
 
 /// Returns a textual representation for [space] used for testing.
-String spaceToText(Space space) => space.toString();
+String spacesToText(Space space) {
+  String text = space.toString();
+  if (text.startsWith('[') && text.endsWith(']')) {
+    // Avoid list-like syntax which collides with the [Features] encoding.
+    return '<$text>';
+  }
+  return text;
+}
 
 /// Returns a textual representation for [fields] used for testing.
-String fieldsToText(Map<String, StaticType> fields) {
-  // TODO(johnniwinther): Enforce that field maps are always sorted.
-  List<String> sortedNames = fields.keys.toList()..sort();
+String fieldsToText(StaticType type, ObjectFieldLookup objectFieldLookup,
+    Set<Key> fieldsOfInterest) {
+  List<Key> sortedNames = fieldsOfInterest.toList()..sort();
   StringBuffer sb = new StringBuffer();
   String comma = '';
   sb.write('{');
-  for (String name in sortedNames) {
-    if (name.startsWith('_')) {
-      // Avoid implementation specific fields, like `Object._identityHashCode`
-      // and `Enum._name`.
-      // TODO(johnniwinther): Support private fields in the test code.
-      continue;
-    }
+  for (Key key in sortedNames) {
+    StaticType? fieldType = type.getField(objectFieldLookup, key);
     sb.write(comma);
-    sb.write(name);
+    sb.write(key.name);
     sb.write(':');
-    sb.write(staticTypeToText(fields[name]!));
+    if (fieldType != null) {
+      sb.write(staticTypeToText(fieldType));
+    } else {
+      sb.write("-");
+    }
     comma = ',';
   }
   sb.write('}');
@@ -48,14 +54,13 @@
 String staticTypeToText(StaticType type) => type.toString();
 
 /// Returns a textual representation of the subtypes of [type] used for testing.
-String? subtypesToText(StaticType type) {
-  List<StaticType> subtypes = type.subtypes.toList();
-  if (subtypes.isEmpty) return null;
-  // TODO(johnniwinther): Sort subtypes.
+String? typesToText(Iterable<StaticType> types) {
+  if (types.isEmpty) return null;
+  // TODO(johnniwinther): Sort types.
   StringBuffer sb = new StringBuffer();
   String comma = '';
   sb.write('{');
-  for (StaticType subtype in subtypes) {
+  for (StaticType subtype in types) {
     sb.write(comma);
     sb.write(staticTypeToText(subtype));
     comma = ',';
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types.dart
new file mode 100644
index 0000000..a08f61a
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types.dart
@@ -0,0 +1,187 @@
+// Copyright (c) 2023, 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 'key.dart';
+import 'shared.dart';
+import 'space.dart';
+import 'static_type.dart';
+import 'witness.dart';
+
+part 'types/bool.dart';
+part 'types/enum.dart';
+part 'types/future_or.dart';
+part 'types/list.dart';
+part 'types/map.dart';
+part 'types/record.dart';
+part 'types/sealed.dart';
+
+/// [StaticType] based on a non-nullable [Type].
+///
+/// All [StaticType] implementation in this library are based on [Type] through
+/// this class. Additionally, the `static_type.dart` library has fixed
+/// [StaticType] implementations for `Object`, `Null`, `Never` and nullable
+/// types.
+class TypeBasedStaticType<Type extends Object> extends NonNullableStaticType {
+  final TypeOperations<Type> _typeOperations;
+  final FieldLookup<Type> _fieldLookup;
+  final Type _type;
+
+  TypeBasedStaticType(this._typeOperations, this._fieldLookup, this._type);
+
+  @override
+  Map<Key, StaticType> get fields => _fieldLookup.getFieldTypes(_type);
+
+  @override
+  StaticType? getAdditionalField(Key key) =>
+      _fieldLookup.getAdditionalFieldType(_type, key);
+
+  /// Returns a [Restriction] value for static types the determines subtypes of
+  /// the [_type]. For instance individual elements of an enum.
+  Restriction get restriction => const Unrestricted();
+
+  @override
+  bool isSubtypeOfInternal(StaticType other) {
+    return other is TypeBasedStaticType<Type> &&
+        _typeOperations.isSubtypeOf(_type, other._type) &&
+        restriction.isSubtypeOf(_typeOperations, other.restriction);
+  }
+
+  @override
+  bool get isSealed => false;
+
+  @override
+  String get name => _typeOperations.typeToString(_type);
+
+  @override
+  int get hashCode => Object.hash(_type, restriction);
+
+  @override
+  bool operator ==(other) {
+    if (identical(this, other)) return true;
+    return other is TypeBasedStaticType<Type> &&
+        _type == other._type &&
+        restriction == other.restriction;
+  }
+
+  Type get typeForTesting => _type;
+
+  @override
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields) {
+    if (!_typeOperations.hasSimpleName(_type)) {
+      buffer.write(name);
+      buffer.write(' _');
+
+      // If we have restrictions on the record type we create an and pattern.
+      String additionalStart = ' && Object(';
+      String additionalEnd = '';
+      String comma = '';
+      for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+        Key key = entry.key;
+        if (key is! ListKey) {
+          buffer.write(additionalStart);
+          additionalStart = '';
+          additionalEnd = ')';
+          buffer.write(comma);
+          comma = ', ';
+
+          buffer.write(key.name);
+          buffer.write(': ');
+          FieldWitness field = entry.value;
+          field.witnessToText(buffer);
+        }
+      }
+      buffer.write(additionalEnd);
+    } else {
+      super.witnessToText(buffer, witness, witnessFields);
+    }
+  }
+}
+
+/// [StaticType] for an object restricted by its [restriction].
+abstract class RestrictedStaticType<Type extends Object,
+    Identity extends Restriction> extends TypeBasedStaticType<Type> {
+  @override
+  final Identity restriction;
+
+  @override
+  final String name;
+
+  RestrictedStaticType(super.typeOperations, super.fieldLookup, super.type,
+      this.restriction, this.name);
+}
+
+/// [StaticType] for an object restricted to a single value.
+class ValueStaticType<Type extends Object, T extends Object>
+    extends RestrictedStaticType<Type, IdentityRestriction<T>> {
+  ValueStaticType(super.typeOperations, super.fieldLookup, super.type,
+      super.restriction, super.name);
+
+  @override
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields) {
+    buffer.write(name);
+
+    // If we have restrictions on the value we create an and pattern.
+    String additionalStart = ' && Object(';
+    String additionalEnd = '';
+    String comma = '';
+    for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+      Key key = entry.key;
+      if (key is! RecordKey) {
+        buffer.write(additionalStart);
+        additionalStart = '';
+        additionalEnd = ')';
+        buffer.write(comma);
+        comma = ', ';
+
+        buffer.write(key.name);
+        buffer.write(': ');
+        FieldWitness field = entry.value;
+        field.witnessToText(buffer);
+      }
+    }
+    buffer.write(additionalEnd);
+  }
+}
+
+/// Interface for a restriction within a subtype relation.
+///
+/// This is used for instance to model enum values within an enum type and
+/// map patterns within a map type.
+abstract class Restriction<Type extends Object> {
+  /// Returns `true` if this [Restriction] covers the whole type.
+  bool get isUnrestricted;
+
+  /// Returns `true` if this restriction is a subtype of [other].
+  bool isSubtypeOf(TypeOperations<Type> typeOperations, Restriction other);
+}
+
+/// The unrestricted [Restriction] that covers all values of a type.
+class Unrestricted implements Restriction<Object> {
+  const Unrestricted();
+
+  @override
+  bool get isUnrestricted => true;
+
+  @override
+  bool isSubtypeOf(TypeOperations<Object> typeOperations, Restriction other) =>
+      other.isUnrestricted;
+}
+
+/// [Restriction] based a unique [identity] value.
+class IdentityRestriction<Identity extends Object>
+    implements Restriction<Object> {
+  final Identity identity;
+
+  const IdentityRestriction(this.identity);
+
+  @override
+  bool get isUnrestricted => false;
+
+  @override
+  bool isSubtypeOf(TypeOperations<Object> typeOperations, Restriction other) =>
+      other.isUnrestricted ||
+      other is IdentityRestriction<Identity> && identity == other.identity;
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/bool.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/bool.dart
new file mode 100644
index 0000000..cfcf23b
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/bool.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2023, 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.
+
+part of '../types.dart';
+
+/// [StaticType] for the `bool` type.
+class BoolStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
+  BoolStaticType(super.typeOperations, super.fieldLookup, super.type);
+
+  @override
+  bool get isSealed => true;
+
+  late StaticType trueType = new ValueStaticType<Type, bool>(_typeOperations,
+      _fieldLookup, _type, const IdentityRestriction<bool>(true), 'true');
+
+  late StaticType falseType = new ValueStaticType<Type, bool>(_typeOperations,
+      _fieldLookup, _type, const IdentityRestriction<bool>(false), 'false');
+
+  @override
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+      [trueType, falseType];
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/enum.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/enum.dart
new file mode 100644
index 0000000..548aafd
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/enum.dart
@@ -0,0 +1,138 @@
+// Copyright (c) 2023, 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.
+
+part of '../types.dart';
+
+/// Interface implemented by analyzer/CFE to support [StaticType]s for enums.
+abstract class EnumOperations<Type extends Object, EnumClass extends Object,
+    EnumElement extends Object, EnumElementValue extends Object> {
+  /// Returns the enum class declaration for the [type] or `null` if
+  /// [type] is not an enum type.
+  EnumClass? getEnumClass(Type type);
+
+  /// Returns the enum elements defined by [enumClass].
+  Iterable<EnumElement> getEnumElements(EnumClass enumClass);
+
+  /// Returns the value defined by the [enumElement]. The encoding is specific
+  /// the implementation of this interface but must ensure constant value
+  /// identity.
+  EnumElementValue getEnumElementValue(EnumElement enumElement);
+
+  /// Returns the declared name of the [enumElement].
+  String getEnumElementName(EnumElement enumElement);
+
+  /// Returns the static type of the [enumElement].
+  Type getEnumElementType(EnumElement enumElement);
+}
+
+/// [EnumInfo] stores information to compute the static type for and the type
+/// of and enum class and its enum elements.
+class EnumInfo<Type extends Object, EnumClass extends Object,
+    EnumElement extends Object, EnumElementValue extends Object> {
+  final TypeOperations<Type> _typeOperations;
+  final FieldLookup<Type> _fieldLookup;
+  final EnumOperations<Type, EnumClass, EnumElement, EnumElementValue>
+      _enumOperations;
+  final EnumClass _enumClass;
+  Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>?
+      _enumElements;
+
+  EnumInfo(this._typeOperations, this._fieldLookup, this._enumOperations,
+      this._enumClass);
+
+  /// Returns a map of the enum elements and their corresponding [StaticType]s
+  /// declared by [_enumClass].
+  Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
+      get enumElements => _enumElements ??= _createEnumElements();
+
+  /// Returns the [StaticType] corresponding to [enumElementValue].
+  EnumElementStaticType<Type, EnumElement> getEnumElement(
+      EnumElementValue enumElementValue) {
+    return enumElements[enumElementValue]!;
+  }
+
+  Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>>
+      _createEnumElements() {
+    Map<EnumElementValue, EnumElementStaticType<Type, EnumElement>> elements =
+        {};
+    for (EnumElement element in _enumOperations.getEnumElements(_enumClass)) {
+      EnumElementValue value = _enumOperations.getEnumElementValue(element);
+      elements[value] = new EnumElementStaticType<Type, EnumElement>(
+          _typeOperations,
+          _fieldLookup,
+          _enumOperations.getEnumElementType(element),
+          new IdentityRestriction<EnumElement>(element),
+          _enumOperations.getEnumElementName(element));
+    }
+    return elements;
+  }
+}
+
+/// [StaticType] for an instantiation of an enum that support access to the
+/// enum values that populate its type through the [subtypes] property.
+class EnumStaticType<Type extends Object, EnumElement extends Object>
+    extends TypeBasedStaticType<Type> {
+  final EnumInfo<Type, Object, EnumElement, Object> _enumInfo;
+  List<StaticType>? _enumElements;
+
+  EnumStaticType(
+      super.typeOperations, super.fieldLookup, super.type, this._enumInfo);
+
+  @override
+  bool get isSealed => true;
+
+  @override
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) => enumElements;
+
+  List<StaticType> get enumElements => _enumElements ??= _createEnumElements();
+
+  List<StaticType> _createEnumElements() {
+    List<StaticType> elements = [];
+    for (EnumElementStaticType<Type, EnumElement> enumElement
+        in _enumInfo.enumElements.values) {
+      // For generic enums, the individual enum elements might not be subtypes
+      // of the concrete enum type. For instance
+      //
+      //    enum E<T> {
+      //      a<int>(),
+      //      b<String>(),
+      //      c<bool>(),
+      //    }
+      //
+      //    method<T extends num>(E<T> e) {
+      //      switch (e) { ... }
+      //    }
+      //
+      // Here the enum elements `E.b` and `E.c` cannot be actual values of `e`
+      // because of the bound `num` on `T`.
+      //
+      // We detect this by checking whether the enum element type is a subtype
+      // of the overapproximation of [_type], in this case whether the element
+      // types are subtypes of `E<num>`.
+      //
+      // Since all type arguments on enum values are fixed, we don't have to
+      // avoid the trivial subtype instantiation `E<Never>`.
+      if (_typeOperations.isSubtypeOf(
+          enumElement._type, _typeOperations.overapproximate(_type))) {
+        // Since the type of the enum element might not itself be a subtype of
+        // [_type], for instance in the example above the type of `Enum.a`,
+        // `Enum<int>`, is not a subtype of `Enum<T>`, we wrap the static type
+        // to establish the subtype relation between the [StaticType] for the
+        // enum element and this [StaticType].
+        elements.add(new WrappedStaticType(enumElement, this));
+      }
+    }
+    return elements;
+  }
+}
+
+/// [StaticType] for a single enum element.
+///
+/// In the [StaticType] model, individual enum elements are represented as
+/// unique subtypes of the enum type, modelled using [EnumStaticType].
+class EnumElementStaticType<Type extends Object, EnumElement extends Object>
+    extends ValueStaticType<Type, EnumElement> {
+  EnumElementStaticType(super.typeOperations, super.fieldLookup, super.type,
+      super.restriction, super.name);
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/future_or.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/future_or.dart
new file mode 100644
index 0000000..5ae8dc2
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/future_or.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, 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.
+
+part of '../types.dart';
+
+/// [StaticType] for a `FutureOr<T>` type for some type `T`.
+///
+/// This is a sealed type where the subtypes for are `T` and `Future<T>`.
+class FutureOrStaticType<Type extends Object>
+    extends TypeBasedStaticType<Type> {
+  /// The type for `T`.
+  final StaticType _typeArgument;
+
+  /// The type for `Future<T>`.
+  final StaticType _futureType;
+
+  FutureOrStaticType(super.typeOperations, super.fieldLookup, super.type,
+      this._typeArgument, this._futureType);
+
+  @override
+  bool get isSealed => true;
+
+  @override
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+      [_typeArgument, _futureType];
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/list.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/list.dart
new file mode 100644
index 0000000..ec23b8a
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/list.dart
@@ -0,0 +1,277 @@
+// Copyright (c) 2023, 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.
+
+part of '../types.dart';
+
+/// [StaticType] for a list type which can be divided into subtypes of
+/// [ListPatternStaticType].
+///
+/// This is used to support exhaustiveness checking for list types by
+/// contextually dividing the list into relevant cases for checking.
+///
+/// For instance, the exhaustiveness can be achieved by a single pattern
+///
+///     case [...]:
+///
+/// or by two disjoint patterns:
+///
+///     case []:
+///     case [_, ...]:
+///
+/// When checking for exhaustiveness, witness candidates are created and tested
+/// against the available cases. This means that the chosen candidates must be
+/// matched by at least one case or the candidate is considered a witness of
+/// non-exhaustiveness.
+///
+/// Looking at the first example, we could choose `[...]`, the list of
+/// arbitrary size, as a candidate. This works for the first example, since the
+/// case `[...]` matches the list of arbitrary size. But if we tried to use this
+/// on the second example it would fail, since neither `[]` nor `[_, ...]` fully
+/// matches the list of arbitrary size.
+///
+/// A solution could be to choose candidates `[]` and `[_, ...]`, the empty list
+/// and the list of 1 or more elements. This would work for the first example,
+/// since `[...]` matches both the empty list and the list of 1 or more
+/// elements. It also works for the second example, since `[]` matches the empty
+/// list and `[_, ...]` matches the list of 1 or more elements.
+///
+/// But now comes a third way of exhaustively matching a list:
+///
+///     case []:
+///     case [_]:
+///     case [_, _, ...]:
+///
+/// and our candidates no longer work, since while `[]` does match the empty
+/// list, neither `[_]` nor `[_, _, ...]` matches the list of 1 or more
+/// elements.
+///
+/// This shows us that there can be no fixed set of witness candidates that we
+/// can use to match a list type.
+///
+/// What we do instead, is to create the set of witness candidates based on the
+/// cases that should match it. We find the maximal number, n, of fixed, i.e.
+/// non-rest, elements in the cases, and then create the lists of sizes 0 to n-1
+/// and the list of n or more elements as the witness candidates.
+class ListTypeStaticType<Type extends Object>
+    extends TypeBasedStaticType<Type> {
+  ListTypeStaticType(super.typeOperations, super.fieldLookup, super.type);
+
+  @override
+  bool get isSealed => true;
+
+  @override
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) {
+    int maxHeadSize = 0;
+    int maxTailSize = 0;
+    for (Key key in keysOfInterest) {
+      if (key is HeadKey) {
+        if (key.index >= maxHeadSize) {
+          maxHeadSize = key.index + 1;
+        }
+      } else if (key is TailKey) {
+        if (key.index >= maxTailSize) {
+          maxTailSize = key.index + 1;
+        }
+      }
+    }
+    int maxSize = maxHeadSize + maxTailSize;
+    List<StaticType> subtypes = [];
+    Type elementType = _typeOperations.getListElementType(_type)!;
+    String typeArgumentText;
+    if (_typeOperations.isDynamic(elementType)) {
+      typeArgumentText = '';
+    } else {
+      typeArgumentText = '<${_typeOperations.typeToString(elementType)}>';
+    }
+    for (int size = 0; size < maxSize; size++) {
+      ListTypeIdentity<Type> identity = new ListTypeIdentity(
+          elementType, typeArgumentText,
+          size: size, hasRest: false);
+      subtypes.add(new ListPatternStaticType<Type>(
+          _typeOperations, _fieldLookup, _type, identity, identity.toString()));
+    }
+    ListTypeIdentity<Type> identity = new ListTypeIdentity(
+        elementType, typeArgumentText,
+        size: maxSize, hasRest: true);
+    subtypes.add(new ListPatternStaticType<Type>(
+        _typeOperations, _fieldLookup, _type, identity, identity.toString()));
+    return subtypes;
+  }
+}
+
+/// [StaticType] for a list pattern type using a [ListTypeIdentity] for its
+/// uniqueness.
+class ListPatternStaticType<Type extends Object>
+    extends RestrictedStaticType<Type, ListTypeIdentity<Type>> {
+  ListPatternStaticType(super.typeOperations, super.fieldLookup, super.type,
+      super.restriction, super.name);
+
+  @override
+  String spaceToText(
+      Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+    StringBuffer buffer = new StringBuffer();
+    buffer.write(restriction.typeArgumentText);
+    buffer.write('[');
+
+    bool first = true;
+    additionalSpaceFields.forEach((Key key, Space space) {
+      if (!first) buffer.write(', ');
+      if (key is RestKey) {
+        buffer.write('...');
+      }
+      buffer.write(space);
+      first = false;
+    });
+
+    buffer.write(']');
+    return buffer.toString();
+  }
+
+  @override
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields) {
+    int maxHeadSize = 0;
+    int maxTailSize = 0;
+    FieldWitness? restWitness;
+    for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+      Key key = entry.key;
+      if (key is HeadKey && key.index >= maxHeadSize) {
+        maxHeadSize = key.index + 1;
+      } else if (key is TailKey && key.index >= maxHeadSize) {
+        maxTailSize = key.index + 1;
+      } else if (key is RestKey) {
+        // TODO(johnniwinther): Can the rest key have head/tail sizes that don't
+        // match the found max head/tail sizes?
+        restWitness = entry.value;
+      }
+    }
+    if (maxHeadSize + maxTailSize < restriction.size) {
+      maxHeadSize = restriction.size - maxTailSize;
+    }
+    buffer.write('[');
+    String comma = '';
+    for (int index = 0; index < maxHeadSize; index++) {
+      buffer.write(comma);
+      Key key = new HeadKey(index);
+      FieldWitness? witness = witnessFields[key];
+      if (witness != null) {
+        witness.witnessToText(buffer);
+      } else {
+        buffer.write('_');
+      }
+      comma = ', ';
+    }
+    if (restriction.hasRest) {
+      buffer.write(comma);
+      buffer.write('...');
+      if (restWitness != null) {
+        restWitness.witnessToText(buffer);
+      }
+      comma = ', ';
+    }
+    for (int index = maxTailSize - 1; index >= 0; index--) {
+      buffer.write(comma);
+      Key key = new TailKey(index);
+      FieldWitness? witness = witnessFields[key];
+      if (witness != null) {
+        witness.witnessToText(buffer);
+      } else {
+        buffer.write('_');
+      }
+      comma = ', ';
+    }
+    buffer.write(']');
+
+    // If we have restrictions on the record type we create an and pattern.
+    String additionalStart = ' && Object(';
+    String additionalEnd = '';
+    comma = '';
+    for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+      Key key = entry.key;
+      if (key is! ListKey) {
+        buffer.write(additionalStart);
+        additionalStart = '';
+        additionalEnd = ')';
+        buffer.write(comma);
+        comma = ', ';
+
+        buffer.write(key.name);
+        buffer.write(': ');
+        FieldWitness field = entry.value;
+        field.witnessToText(buffer);
+      }
+    }
+    buffer.write(additionalEnd);
+  }
+}
+
+/// Identity object used for creating a unique [ListPatternStaticType] for a
+/// list pattern.
+///
+/// The uniqueness is defined by the element type, the number of elements at the
+/// start of the list, whether the list pattern has a rest element, and the
+/// number elements at the end of the list, after the rest element.
+class ListTypeIdentity<Type extends Object> implements Restriction<Type> {
+  final Type elementType;
+  final int size;
+  final bool hasRest;
+  final String typeArgumentText;
+
+  ListTypeIdentity(this.elementType, this.typeArgumentText,
+      {required this.size, required this.hasRest});
+
+  @override
+  late final int hashCode = Object.hash(elementType, size, hasRest);
+
+  @override
+  bool get isUnrestricted {
+    // The map pattern containing only a rest pattern covers the whole type.
+    return hasRest && size == 0;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is ListTypeIdentity<Type> &&
+        elementType == other.elementType &&
+        size == other.size &&
+        hasRest == other.hasRest;
+  }
+
+  @override
+  bool isSubtypeOf(TypeOperations<Type> typeOperations, Restriction other) {
+    if (other.isUnrestricted) return true;
+    if (other is! ListTypeIdentity<Type>) return false;
+    if (!typeOperations.isSubtypeOf(elementType, other.elementType)) {
+      return false;
+    }
+    if (other.hasRest) {
+      return size >= other.size;
+    } else if (hasRest) {
+      return false;
+    } else {
+      return size == other.size;
+    }
+  }
+
+  @override
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.write(typeArgumentText);
+    sb.write('[');
+    String comma = '';
+    for (int i = 0; i < size; i++) {
+      sb.write(comma);
+      sb.write('()');
+      comma = ', ';
+    }
+    if (hasRest) {
+      sb.write(comma);
+      sb.write('...');
+      comma = ', ';
+    }
+    sb.write(']');
+    return sb.toString();
+  }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/map.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/map.dart
new file mode 100644
index 0000000..e15db86
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/map.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2023, 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.
+
+part of '../types.dart';
+
+/// [StaticType] for a map pattern type using a [MapTypeIdentity] for its
+/// uniqueness.
+class MapPatternStaticType<Type extends Object>
+    extends RestrictedStaticType<Type, MapTypeIdentity<Type>> {
+  MapPatternStaticType(super.typeOperations, super.fieldLookup, super.type,
+      super.restriction, super.name);
+
+  @override
+  String spaceToText(
+      Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+    StringBuffer buffer = new StringBuffer();
+    buffer.write(restriction.typeArgumentsText);
+    buffer.write('{');
+
+    bool first = true;
+    additionalSpaceFields.forEach((Key key, Space space) {
+      if (!first) buffer.write(', ');
+      buffer.write('$key: $space');
+      first = false;
+    });
+    if (restriction.hasRest) {
+      if (!first) buffer.write(', ');
+      buffer.write('...');
+    }
+
+    buffer.write('}');
+    return buffer.toString();
+  }
+
+  @override
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields) {
+    buffer.write('{');
+    String comma = '';
+    for (MapKey key in restriction.keys) {
+      buffer.write(comma);
+      buffer.write(key.valueAsText);
+      buffer.write(': ');
+      FieldWitness? witness = witnessFields[key];
+      if (witness != null) {
+        witness.witnessToText(buffer);
+      } else {
+        buffer.write('_');
+      }
+      comma = ', ';
+    }
+    if (restriction.hasRest) {
+      buffer.write(comma);
+      buffer.write('...');
+    }
+    buffer.write('}');
+
+    // If we have restrictions on the record type we create an and pattern.
+    String additionalStart = ' && Object(';
+    String additionalEnd = '';
+    comma = '';
+    for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+      Key key = entry.key;
+      if (key is! MapKey) {
+        buffer.write(additionalStart);
+        additionalStart = '';
+        additionalEnd = ')';
+        buffer.write(comma);
+        comma = ', ';
+
+        buffer.write(key.name);
+        buffer.write(': ');
+        FieldWitness field = entry.value;
+        field.witnessToText(buffer);
+      }
+    }
+    buffer.write(additionalEnd);
+  }
+}
+
+/// Identity object used for creating a unique [MapPatternStaticType] for a
+/// map pattern.
+///
+/// The uniqueness is defined by the key and value types, the key values of
+/// the map pattern, and whether the map pattern has a rest element.
+///
+/// This identity ensures that we can detect overlap between map patterns with
+/// the same set of keys.
+class MapTypeIdentity<Type extends Object> implements Restriction<Type> {
+  final Type keyType;
+  final Type valueType;
+  final Set<MapKey> keys;
+  final bool hasRest;
+  final String typeArgumentsText;
+
+  MapTypeIdentity(
+      this.keyType, this.valueType, this.keys, this.typeArgumentsText,
+      {required this.hasRest});
+
+  @override
+  late final int hashCode =
+      Object.hash(keyType, valueType, Object.hashAllUnordered(keys), hasRest);
+
+  @override
+  bool get isUnrestricted {
+    // The map pattern containing only a rest pattern covers the whole type.
+    return hasRest && keys.isEmpty;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    if (other is! MapTypeIdentity<Type>) return false;
+    if (keyType != other.keyType ||
+        valueType != other.valueType ||
+        hasRest != other.hasRest) {
+      return false;
+    }
+    if (keys.length != other.keys.length) return false;
+    return keys.containsAll(other.keys);
+  }
+
+  @override
+  bool isSubtypeOf(TypeOperations<Type> typeOperations, Restriction other) {
+    if (other.isUnrestricted) return true;
+    if (other is! MapTypeIdentity<Type>) return false;
+    if (!typeOperations.isSubtypeOf(keyType, other.keyType)) return false;
+    if (!typeOperations.isSubtypeOf(valueType, other.valueType)) return false;
+    if (other.hasRest) {
+      return keys.containsAll(other.keys);
+    } else if (hasRest) {
+      return false;
+    } else {
+      return keys.length == other.keys.length && keys.containsAll(other.keys);
+    }
+  }
+
+  @override
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.write(typeArgumentsText);
+    sb.write('{');
+    String comma = '';
+    for (MapKey key in keys) {
+      sb.write(comma);
+      sb.write(key);
+      sb.write(': ()');
+      comma = ', ';
+    }
+    if (hasRest) {
+      sb.write(comma);
+      sb.write('...');
+      comma = ', ';
+    }
+    sb.write('}');
+    return sb.toString();
+  }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/record.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/record.dart
new file mode 100644
index 0000000..a7a93a3
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/record.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2023, 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.
+
+part of '../types.dart';
+
+/// [StaticType] for a record type.
+///
+/// This models that type aspect of the record using only the structure of the
+/// record type. This means that the type for `(Object, String)` and
+/// `(String, int)` will be subtypes of each other.
+///
+/// This is necessary to avoid invalid conclusions on the disjointness of
+/// spaces base on the their types. For instance in
+///
+///     method((String, Object) o) {
+///       if (o case (Object _, String s)) {}
+///     }
+///
+/// the case is not empty even though `(String, Object)` and `(Object, String)`
+/// are not related type-wise.
+///
+/// Not that the fields of the record types _are_ using the type, so that
+/// the `$1` field of `(String, Object)` is known to contain only `String`s.
+class RecordStaticType<Type extends Object> extends TypeBasedStaticType<Type> {
+  RecordStaticType(super.typeOperations, super.fieldLookup, super.type);
+
+  @override
+  bool get isRecord => true;
+
+  @override
+  bool isSubtypeOfInternal(StaticType other) {
+    if (other is! RecordStaticType<Type>) {
+      return false;
+    }
+    if (fields.length != other.fields.length) {
+      return false;
+    }
+    for (MapEntry<Key, StaticType> field in fields.entries) {
+      StaticType? type = other.fields[field.key];
+      if (type == null) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @override
+  String spaceToText(
+      Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
+    StringBuffer buffer = new StringBuffer();
+    buffer.write('(');
+    String comma = '';
+    fields.forEach((Key key, StaticType staticType) {
+      if (key is RecordIndexKey) {
+        buffer.write(comma);
+        comma = ', ';
+        buffer.write('${spaceFields[key] ?? staticType}');
+      } else if (key is RecordNameKey) {
+        buffer.write(comma);
+        comma = ', ';
+        buffer.write('${key.name}: ${spaceFields[key] ?? staticType}');
+      }
+    });
+    buffer.write(')');
+    String additionalStart = '(';
+    String additionalEnd = '';
+    comma = '';
+    spaceFields.forEach((Key key, Space value) {
+      if (key is! RecordKey) {
+        buffer.write(additionalStart);
+        additionalStart = '';
+        additionalEnd = ')';
+        buffer.write(comma);
+        comma = ', ';
+        buffer.write('${key.name}: ${value}');
+      }
+    });
+    additionalSpaceFields.forEach((Key key, Space value) {
+      if (key is! RecordKey) {
+        buffer.write(additionalStart);
+        additionalStart = '';
+        additionalEnd = ')';
+        buffer.write(comma);
+        comma = ', ';
+        buffer.write('${key.name}: ${value}');
+      }
+    });
+    buffer.write(additionalEnd);
+    return buffer.toString();
+  }
+
+  @override
+  void witnessToText(StringBuffer buffer, FieldWitness witness,
+      Map<Key, FieldWitness> witnessFields) {
+    buffer.write('(');
+    String comma = '';
+    for (Key key in fields.keys) {
+      if (key is RecordIndexKey) {
+        buffer.write(comma);
+        comma = ', ';
+
+        FieldWitness? field = witnessFields[key];
+        if (field != null) {
+          field.witnessToText(buffer);
+        } else {
+          buffer.write('_');
+        }
+      } else if (key is RecordNameKey) {
+        buffer.write(comma);
+        comma = ', ';
+
+        buffer.write(key.name);
+        buffer.write(': ');
+        FieldWitness? field = witnessFields[key];
+        if (field != null) {
+          field.witnessToText(buffer);
+        } else {
+          buffer.write('_');
+        }
+      }
+    }
+    buffer.write(')');
+
+    // If we have restrictions on the record type we create an and pattern.
+    String additionalStart = ' && Object(';
+    String additionalEnd = '';
+    comma = '';
+    for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
+      Key key = entry.key;
+      if (key is! RecordKey) {
+        buffer.write(additionalStart);
+        additionalStart = '';
+        additionalEnd = ')';
+        buffer.write(comma);
+        comma = ', ';
+
+        buffer.write(key.name);
+        buffer.write(': ');
+        FieldWitness field = entry.value;
+        field.witnessToText(buffer);
+      }
+    }
+    buffer.write(additionalEnd);
+  }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/types/sealed.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/types/sealed.dart
new file mode 100644
index 0000000..5cdafa9
--- /dev/null
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/types/sealed.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2023, 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.
+
+part of '../types.dart';
+
+/// Interface implemented by analyzer/CFE to support [StaticType]s for sealed
+/// classes.
+abstract class SealedClassOperations<Type extends Object,
+    Class extends Object> {
+  /// Returns the sealed class declaration for [type] or `null` if [type] is not
+  /// a sealed class type.
+  Class? getSealedClass(Type type);
+
+  /// Returns the direct subclasses of [sealedClass] that either extend,
+  /// implement or mix it in.
+  List<Class> getDirectSubclasses(Class sealedClass);
+
+  /// Returns the instance of [subClass] that implements [sealedClassType].
+  ///
+  /// `null` might be returned if [subClass] cannot implement [sealedClassType].
+  /// For instance
+  ///
+  ///     sealed class A<T> {}
+  ///     class B<T> extends A<T> {}
+  ///     class C extends A<int> {}
+  ///
+  /// here `C` has no implementation of `A<String>`.
+  ///
+  /// It is assumed that `TypeOperations.isSealedClass` is `true` for
+  /// [sealedClassType] and that [subClass] is in `getDirectSubclasses` for
+  /// `getSealedClass` of [sealedClassType].
+  Type? getSubclassAsInstanceOf(Class subClass, Type sealedClassType);
+}
+
+/// [SealedClassInfo] stores information to compute the static type for a
+/// sealed class.
+class SealedClassInfo<Type extends Object, Class extends Object> {
+  final SealedClassOperations<Type, Class> _sealedClassOperations;
+  final Class _sealedClass;
+  List<Class>? _subClasses;
+
+  SealedClassInfo(this._sealedClassOperations, this._sealedClass);
+
+  /// Returns the classes that directly extends, implements or mix in
+  /// [_sealedClass].
+  Iterable<Class> get subClasses =>
+      _subClasses ??= _sealedClassOperations.getDirectSubclasses(_sealedClass);
+}
+
+/// [StaticType] for a sealed class type.
+class SealedClassStaticType<Type extends Object, Class extends Object>
+    extends TypeBasedStaticType<Type> {
+  final ExhaustivenessCache<Type, dynamic, dynamic, dynamic, Class> _cache;
+  final SealedClassOperations<Type, Class> _sealedClassOperations;
+  final SealedClassInfo<Type, Class> _sealedInfo;
+  Iterable<StaticType>? _subtypes;
+
+  SealedClassStaticType(super.typeOperations, super.fieldLookup, super.type,
+      this._cache, this._sealedClassOperations, this._sealedInfo);
+
+  @override
+  bool get isSealed => true;
+
+  @override
+  Iterable<StaticType> getSubtypes(Set<Key> keysOfInterest) =>
+      _subtypes ??= _createSubtypes();
+
+  List<StaticType> _createSubtypes() {
+    List<StaticType> subtypes = [];
+    for (Class subClass in _sealedInfo.subClasses) {
+      Type? subtype =
+          _sealedClassOperations.getSubclassAsInstanceOf(subClass, _type);
+      if (subtype != null) {
+        if (!_typeOperations.isGeneric(subtype)) {
+          // If the subtype is not generic, we can test whether it can be an
+          // actual value of [_type] by testing whether it is a subtype of the
+          // overapproximation of [_type].
+          //
+          // For instance
+          //
+          //     sealed class A<T> {}
+          //     class B extends A<num> {}
+          //     class C<T extends num> A<T> {}
+          //
+          //     method<T extends String>(A<T> a) {
+          //       switch (a) {
+          //         case B: // Not needed, B cannot inhabit A<T>.
+          //         case C: // Needed, C<Never> inhabits A<T>.
+          //       }
+          //     }
+          if (!_typeOperations.isSubtypeOf(
+              subtype, _typeOperations.overapproximate(_type))) {
+            continue;
+          }
+        }
+        StaticType staticType = _cache.getStaticType(subtype);
+        // Since the type of the [subtype] might not itself be a subtype of
+        // [_type], for instance in the example above the type of `case C:`,
+        // `C<num>`, is not a subtype of `A<T>`, we wrap the static type
+        // to establish the subtype relation between the [StaticType] for the
+        // enum element and this [StaticType].
+        subtypes.add(new WrappedStaticType(staticType, this));
+      }
+    }
+    return subtypes;
+  }
+}
diff --git a/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart b/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart
index 747cce9..a159687 100644
--- a/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart
+++ b/_fe_analyzer_shared/lib/src/exhaustiveness/witness.dart
@@ -1,261 +1,18 @@
 // Copyright (c) 2023, 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 'exhaustive.dart';
-import 'space.dart';
+
+import 'key.dart';
+import 'path.dart';
 import 'static_type.dart';
 
-/// Returns `true` if [caseSpaces] exhaustively covers all possible values of
-/// [valueSpace].
-bool isExhaustiveNew(Space valueSpace, List<Space> caseSpaces) {
-  return checkExhaustiveness(valueSpace, caseSpaces) == null;
-}
-
-/// Checks the [cases] representing a series of switch cases to see if they
-/// exhaustively cover all possible values of the matched [valueType]. Also
-/// checks to see if any case can't be matched because it's covered by previous
-/// cases.
-///
-/// Returns a list of any unreachable case or non-exhaustive match errors.
-/// Returns an empty list if all cases are reachable and the cases are
-/// exhaustive.
-List<ExhaustivenessError> reportErrorsNew(
-    StaticType valueType, List<Space> caseSpaces) {
-  List<ExhaustivenessError> errors = <ExhaustivenessError>[];
-
-  Pattern valuePattern = new Pattern(valueType, {}, []);
-  List<List<Pattern>> cases =
-      caseSpaces.map((space) => [_spaceToPattern(space)]).toList();
-
-  for (int i = 1; i < cases.length; i++) {
-    // See if this case is covered by previous ones.
-    if (_unmatched(cases.sublist(0, i), cases[i]) == null) {
-      errors.add(new UnreachableCaseErrorNew(valueType, caseSpaces, i));
-    }
-  }
-
-  String? witness = _unmatched(cases, [valuePattern]);
-  if (witness != null) {
-    errors.add(new NonExhaustiveErrorNew(valueType, caseSpaces, witness));
-  }
-
-  return errors;
-}
-
-/// Determines if [caseSpaces] is exhaustive over all values contained by
-/// [valueSpace]. If so, returns `null`. Otherwise, returns a string describing
-/// an example of one value that isn't matched by anything in [caseSpaces].
-// TODO(johnniwinther): Remove this method?
-String? checkExhaustiveness(Space valueSpace, List<Space> caseSpaces) {
-  Pattern value = _spaceToPattern(valueSpace);
-  List<List<Pattern>> cases =
-      caseSpaces.map((space) => [_spaceToPattern(space)]).toList();
-
-  String? witness = _unmatched(cases, [value]);
-
-  // Uncomment this to have it print out the witness for non-exhaustive matches.
-  // if (witness != null) print(witness);
-
-  return witness;
-}
-
-/// Convert the prototype's original [Space] representation to the [Pattern]
-/// representation used here.
-///
-/// This is only a convenience to run the existing test code which creates
-/// [Space]s using the new algorithm.
-Pattern _spaceToPattern(Space space, [List<String> path = const []]) {
-  if (space is! ExtractSpace) {
-    // TODO(johnniwinther): Remove this together with the old algorithm:
-    // The old algorithm creates UnionSpaces internally but they are never used
-    // directly as entrypoint arguments, so this function support them.
-    throw new ArgumentError('Space should be an ExtractSpace.');
-  }
-
-  Map<String, Pattern> fields = {
-    for (String name in space.fields.keys)
-      name: _spaceToPattern(space.fields[name]!, [...path, name])
-  };
-  return new Pattern(space.type, fields, path);
-}
-
-/// Tries to find a pattern containing at least one value matched by
-/// [valuePatterns] that is not matched by any of the patterns in [caseRows].
-///
-/// If found, returns it. This is a witness example showing that [caseRows] is
-/// not exhaustive over all values in [valuePatterns]. If it returns `null`,
-/// then [caseRows] exhaustively covers [valuePatterns].
-String? _unmatched(List<List<Pattern>> caseRows, List<Pattern> valuePatterns,
-    [List<Predicate> witnessPredicates = const []]) {
-  // If there are no more columns, then we've tested all the predicates we have
-  // to test.
-  if (valuePatterns.isEmpty) {
-    // If there are still any rows left, then it means every remaining value
-    // will go to one of those rows' bodies, so we have successfully matched.
-    if (caseRows.isNotEmpty) return null;
-
-    // If we ran out of rows too, then it means [witnessPredicates] is now a
-    // complete description of at least one value that slipped past all the
-    // rows.
-    return _witnessString(witnessPredicates);
-  }
-
-  // Look down the first column of tests.
-  Pattern valuePattern = valuePatterns[0];
-
-  // TODO(johnniwinther): Right now, this brute force expands all subtypes of
-  // sealed types and considers them individually. It would be faster to look
-  // at the types of the patterns in the first column of each row and only
-  // expand subtypes that are actually tested.
-  // Split the type into its sealed subtypes and consider each one separately.
-  // This enables it to filter rows more effectively.
-  List<StaticType> subtypes = _expandSealedSubtypes(valuePattern.type);
-  for (StaticType subtype in subtypes) {
-    String? result =
-        _filterByType(subtype, caseRows, valuePatterns, witnessPredicates);
-
-    // If we found a witness for a subtype that no rows match, then we can
-    // stop. There may be others but we don't need to find more.
-    if (result != null) return result;
-  }
-
-  // If we get here, no subtype yielded a witness, so we must have matched
-  // everything.
-  return null;
-}
-
-String? _filterByType(StaticType type, List<List<Pattern>> caseRows,
-    List<Pattern> valuePatterns, List<Predicate> witnessPredicates) {
-  // Extend the witness with the type we're matching.
-  List<Predicate> extendedWitness = [
-    ...witnessPredicates,
-    new Predicate(valuePatterns.first.path, type)
-  ];
-
-  // Discard any row that may not match by type. We only keep rows that *must*
-  // match because a row that could potentially fail to match will not help us
-  // prove exhaustiveness.
-  List<List<Pattern>> remainingRows = <List<Pattern>>[];
-  for (List<Pattern> row in caseRows) {
-    Pattern firstPattern = row[0];
-
-    // If the row's type is a supertype of the value pattern's type then it
-    // must match.
-    if (type.isSubtypeOf(firstPattern.type)) {
-      remainingRows.add(row);
-    }
-  }
-
-  // We have now filtered by the type test of the first column of patterns, but
-  // some of those may also have field subpatterns. If so, lift those out so we
-  // can recurse into them.
-  Set<String> fieldNames = {
-    ...valuePatterns.first.fields.keys,
-    for (List<Pattern> row in remainingRows) ...row.first.fields.keys
-  };
-
-  // Sorting isn't necessary, but makes the behavior deterministic.
-  List<String> sorted = fieldNames.toList()..sort();
-
-  // Remove the first column from the value list and replace it with any
-  // expanded fields.
-  valuePatterns = [
-    ..._expandFields(sorted, valuePatterns.first, type),
-    ...valuePatterns.skip(1)
-  ];
-
-  // Remove the first column from each row and replace it with any expanded
-  // fields.
-  for (int i = 0; i < remainingRows.length; i++) {
-    remainingRows[i] = [
-      ..._expandFields(
-          sorted, remainingRows[i].first, remainingRows[i].first.type),
-      ...remainingRows[i].skip(1)
-    ];
-  }
-
-  // Proceed to the next column.
-  return _unmatched(remainingRows, valuePatterns, extendedWitness);
-}
-
-/// Given a list of [fieldNames] and a [pattern], generates a list of patterns,
-/// one for each named field.
-///
-/// When pattern contains a field with that name, extracts it into the
-/// resulting list. Otherwise, the pattern doesn't care
-/// about that field, so inserts a default pattern that matches all values for
-/// the field.
-///
-/// In other words, this unpacks a set of fields so that the main algorithm can
-/// add them to the worklist.
-List<Pattern> _expandFields(
-    List<String> fieldNames, Pattern pattern, StaticType type) {
-  List<Pattern> result = <Pattern>[];
-  for (String fieldName in fieldNames) {
-    Pattern? field = pattern.fields[fieldName];
-    if (field != null) {
-      result.add(field);
-    } else {
-      // This pattern doesn't test this field, so add a pattern for the
-      // field that matches all values. This way the columns stay aligned.
-      result.add(
-        new Pattern(
-          type.fields[fieldName] ?? StaticType.nullableObject,
-          {},
-          [...pattern.path, fieldName],
-        ),
-      );
-    }
-  }
-
-  return result;
-}
-
-/// Recursively expands [type] with its subtypes if its sealed.
-///
-/// Otherwise, just returns [type].
-List<StaticType> _expandSealedSubtypes(StaticType type) {
-  if (!type.isSealed) return [type];
-
-  return {
-    for (StaticType subtype in type.subtypes) ..._expandSealedSubtypes(subtype)
-  }.toList();
-}
-
-/// The main pattern for matching types and destructuring.
-///
-/// It has a type which determines the type of values it contains. The type may
-/// be [StaticType.top] to indicate that it doesn't filter by type.
-///
-/// It may also contain zero or more named fields. The pattern then only matches
-/// values where the field values are matched by the corresponding field
-/// patterns.
-// TODO(johnniwinther): Rename this to avoid name clash with Pattern from
-//  dart:core.
-class Pattern {
-  /// The type of values the pattern matches.
-  final StaticType type;
-
-  /// Any field subpatterns the pattern matches.
-  final Map<String, Pattern> fields;
-
-  /// The path of getters that led from the original matched value to value
-  /// matched by this pattern. Used to generate a human-readable witness.
-  final List<String> path;
-
-  Pattern(this.type, this.fields, this.path);
-
-  @override
-  String toString() => 'Pattern(type=$type,fields=$fields,path=$path)';
-}
-
 /// Describes a pattern that matches the value or a field accessed from it.
 ///
 /// Used only to generate the witness description.
 class Predicate {
   /// The path of getters that led from the original matched value to the value
   /// tested by this predicate.
-  final List<String> path;
+  final Path path;
 
   /// The type this predicate tests.
   // TODO(johnniwinther): In order to model exhaustiveness on enum types,
@@ -269,8 +26,10 @@
   String toString() => 'Predicate(path=$path,type=$type)';
 }
 
-/// Builds a human-friendly pattern-like string for the witness matched by
-/// [predicates].
+/// Witness that show an unmatched case.
+///
+/// This is used to builds a human-friendly pattern-like string for the witness
+/// matched by [_predicates].
 ///
 /// For example, given:
 ///
@@ -282,54 +41,45 @@
 ///     ['z', 'x'] is C
 ///     ['z', 'y'] is B
 ///
-/// Produces:
+/// the [toString] produces:
 ///
 ///     'U(w: T(x: B, y: B), z: T(x: C, y: B))'
-String _witnessString(List<Predicate> predicates) {
-  Witness witness = new Witness();
+class Witness {
+  final List<Predicate> _predicates;
+  late final FieldWitness _witness = _buildWitness();
 
-  for (Predicate predicate in predicates) {
-    Witness here = witness;
-    for (String field in predicate.path) {
-      here = here.fields.putIfAbsent(field, () => new Witness());
+  Witness(this._predicates);
+
+  FieldWitness _buildWitness() {
+    FieldWitness witness = new FieldWitness();
+
+    for (Predicate predicate in _predicates) {
+      FieldWitness here = witness;
+      for (Key field in predicate.path.toList()) {
+        here = here.fields.putIfAbsent(field, () => new FieldWitness());
+      }
+      here.type = predicate.type;
     }
-    here.type = predicate.type;
+    return witness;
   }
 
-  StringBuffer buffer = new StringBuffer();
-  witness.buildString(buffer);
-  return buffer.toString();
+  @override
+  String toString() => _witness.toString();
 }
 
 /// Helper class used to turn a list of [Predicates] into a string.
-class Witness {
+class FieldWitness {
   StaticType type = StaticType.nullableObject;
-  final Map<String, Witness> fields = {};
+  final Map<Key, FieldWitness> fields = {};
 
-  void buildString(StringBuffer buffer) {
-    if (type != StaticType.nullableObject) {
-      buffer.write(type);
-    }
-
-    if (fields.isNotEmpty) {
-      buffer.write('(');
-      bool first = true;
-      fields.forEach((name, field) {
-        if (!first) buffer.write(', ');
-        first = false;
-
-        buffer.write(name);
-        buffer.write(': ');
-        field.buildString(buffer);
-      });
-      buffer.write(')');
-    }
+  void witnessToText(StringBuffer buffer) {
+    type.witnessToText(buffer, this, fields);
   }
 
   @override
   String toString() {
     StringBuffer sb = new StringBuffer();
-    buildString(sb);
+    witnessToText(sb);
     return sb.toString();
   }
 }
diff --git a/_fe_analyzer_shared/lib/src/experiments/flags.dart b/_fe_analyzer_shared/lib/src/experiments/flags.dart
index f1125e5..cb1f396 100644
--- a/_fe_analyzer_shared/lib/src/experiments/flags.dart
+++ b/_fe_analyzer_shared/lib/src/experiments/flags.dart
@@ -11,7 +11,7 @@
 enum ExperimentalFlag {
   classModifiers(
       name: 'class-modifiers',
-      isEnabledByDefault: false,
+      isEnabledByDefault: true,
       isExpired: false,
       experimentEnabledVersion: const Version(3, 0),
       experimentReleasedVersion: const Version(3, 0)),
@@ -123,21 +123,21 @@
 
   patterns(
       name: 'patterns',
-      isEnabledByDefault: false,
+      isEnabledByDefault: true,
       isExpired: false,
       experimentEnabledVersion: const Version(3, 0),
       experimentReleasedVersion: const Version(3, 0)),
 
   records(
       name: 'records',
-      isEnabledByDefault: false,
+      isEnabledByDefault: true,
       isExpired: false,
       experimentEnabledVersion: const Version(3, 0),
-      experimentReleasedVersion: const Version(2, 19)),
+      experimentReleasedVersion: const Version(3, 0)),
 
   sealedClass(
       name: 'sealed-class',
-      isEnabledByDefault: false,
+      isEnabledByDefault: true,
       isExpired: false,
       experimentEnabledVersion: const Version(3, 0),
       experimentReleasedVersion: const Version(3, 0)),
diff --git a/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index 143f407..c2be3f9 100644
--- a/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -241,8 +241,6 @@
   ///
   /// [matchedType] should be the static type of the value being matched.
   /// [staticType] should be the static type of the variable pattern itself.
-  /// [initializerExpression] should be the initializer expression being matched
-  /// (or `null` if there is no expression being matched to this variable).
   /// [isFinal] indicates whether the variable is final, and [isImplicitlyTyped]
   /// indicates whether the variable has an explicit type annotation.
   ///
@@ -257,7 +255,6 @@
   int declaredVariablePattern(
       {required Type matchedType,
       required Type staticType,
-      Expression? initializerExpression,
       bool isFinal = false,
       bool isLate = false,
       required bool isImplicitlyTyped});
@@ -1185,19 +1182,16 @@
   int declaredVariablePattern(
       {required Type matchedType,
       required Type staticType,
-      Expression? initializerExpression,
       bool isFinal = false,
       bool isLate = false,
       required bool isImplicitlyTyped}) {
     return _wrap(
         'declaredVariablePattern(matchedType: $matchedType, '
-        'staticType: $staticType, '
-        'initializerExpression: $initializerExpression, isFinal: $isFinal, '
+        'staticType: $staticType, isFinal: $isFinal, '
         'isLate: $isLate, isImplicitlyTyped: $isImplicitlyTyped)',
         () => _wrapped.declaredVariablePattern(
             matchedType: matchedType,
             staticType: staticType,
-            initializerExpression: initializerExpression,
             isFinal: isFinal,
             isLate: isLate,
             isImplicitlyTyped: isImplicitlyTyped),
@@ -3680,27 +3674,6 @@
   /// [ReferenceWithType] object referring to the scrutinee.  Otherwise `null`.
   ReferenceWithType<Type>? _scrutineeReference;
 
-  /// If a pattern is being analyzed, and the scrutinee is something that might
-  /// be type promoted as a consequence of the pattern match, [SsaNode]
-  /// reflecting the state of the pattern match at the time that
-  /// [_scrutineeReference] was captured.  Otherwise `null`.
-  ///
-  /// This is necessary to detect situations where the scrutinee is modified
-  /// after the beginning of a switch statement and before choosing the case to
-  /// execute (e.g. in a guard clause), and therefore further pattern matches
-  /// should not promote the scrutinee (since they are acting on a cached value
-  /// that no longer matches the scrutinee expression).  For example:
-  ///
-  ///     switch (v) {
-  ///       case int _: // promotes `v` to `int`
-  ///         break;
-  ///       case _ when f(v = ...): // reassigns `v`
-  ///         break;
-  ///       case String _: // does not promote `v` to `String`
-  ///         break;
-  ///     }
-  SsaNode<Type>? _scrutineeSsaNode;
-
   /// The most recently visited expression for which an [ExpressionInfo] object
   /// exists, or `null` if no expression has been visited that has a
   /// corresponding [ExpressionInfo] object.
@@ -3891,7 +3864,6 @@
   int declaredVariablePattern(
       {required Type matchedType,
       required Type staticType,
-      Expression? initializerExpression,
       bool isFinal = false,
       bool isLate = false,
       required bool isImplicitlyTyped}) {
@@ -3997,7 +3969,6 @@
     assert(_current.reachable.parent == null);
     assert(_unmatched == null);
     assert(_scrutineeReference == null);
-    assert(_scrutineeSsaNode == null);
   }
 
   @override
@@ -4130,7 +4101,8 @@
     // Note that we don't need to take any action to handle
     // `before(E1) = matched(P)`, because we store both the "matched" state for
     // patterns and the "before" state for expressions in `_current`.
-    _pushPattern(_pushScrutinee(scrutinee, scrutineeType));
+    _pushPattern(_pushScrutinee(scrutinee, scrutineeType,
+        allowScrutineePromotion: true));
   }
 
   @override
@@ -4467,7 +4439,7 @@
 
   @override
   void patternAssignment_afterRhs(Expression rhs, Type rhsType) {
-    _pushPattern(_pushScrutinee(rhs, rhsType));
+    _pushPattern(_pushScrutinee(rhs, rhsType, allowScrutineePromotion: false));
   }
 
   @override
@@ -4478,7 +4450,8 @@
 
   @override
   void patternForIn_afterExpression(Type elementType) {
-    _pushPattern(_pushScrutinee(null, elementType));
+    _pushPattern(
+        _pushScrutinee(null, elementType, allowScrutineePromotion: false));
   }
 
   @override
@@ -4490,7 +4463,8 @@
   @override
   void patternVariableDeclaration_afterInitializer(
       Expression initializer, Type initializerType) {
-    _pushPattern(_pushScrutinee(initializer, initializerType));
+    _pushPattern(_pushScrutinee(initializer, initializerType,
+        allowScrutineePromotion: false));
   }
 
   @override
@@ -4526,6 +4500,13 @@
       required Type knownType,
       bool matchFailsIfWrongType = true,
       bool matchMayFailEvenIfCorrectType = false}) {
+    if (operations.classifyType(matchedType) ==
+        TypeClassification.nonNullable) {
+      // The matched type is non-nullable, so promote to a non-nullable type.
+      // This allows for code like `case int? x?` to promote `x` to
+      // non-nullable.
+      knownType = operations.promoteToNonNull(knownType);
+    }
     _PatternContext<Type> context = _stack.last as _PatternContext<Type>;
     ReferenceWithType<Type> matchedValueReference =
         context.createReference(matchedType);
@@ -4694,7 +4675,7 @@
   void switchStatement_expressionEnd(
       Statement? switchStatement, Expression scrutinee, Type scrutineeType) {
     EqualityInfo<Type> matchedValueInfo =
-        _pushScrutinee(scrutinee, scrutineeType);
+        _pushScrutinee(scrutinee, scrutineeType, allowScrutineePromotion: true);
     _current = _current.split();
     _SwitchStatementContext<Type> context = new _SwitchStatementContext<Type>(
         _current.reachable.parent!, _current, matchedValueInfo);
@@ -4880,9 +4861,6 @@
     if (_scrutineeReference != null) {
       print('  scrutineeReference: $_scrutineeReference');
     }
-    if (_scrutineeSsaNode != null) {
-      print('  scrutineeSsaNode: $_scrutineeSsaNode');
-    }
     if (_expressionWithInfo != null) {
       print('  expressionWithInfo: $_expressionWithInfo');
     }
@@ -5250,7 +5228,6 @@
     _ScrutineeContext<Type> context =
         _stack.removeLast() as _ScrutineeContext<Type>;
     _scrutineeReference = context.previousScrutineeReference;
-    _scrutineeSsaNode = context.previousScrutineeSsaNode;
   }
 
   /// Updates the [_stack] to reflect the fact that flow analysis is entering
@@ -5271,25 +5248,32 @@
   /// that's being matched directly, as happens when in `for-in` loops).
   /// [scrutineeType] should be the static type of the scrutinee.
   ///
+  /// [allowScrutineePromotion] indicates whether pattern matches should cause
+  /// the scrutinee to be promoted.
+  ///
   /// The returned value is the [EqualityInfo] representing the value being
   /// matched.  It should be passed to [_pushPattern].
-  EqualityInfo<Type> _pushScrutinee(Expression? scrutinee, Type scrutineeType) {
+  EqualityInfo<Type> _pushScrutinee(Expression? scrutinee, Type scrutineeType,
+      {required bool allowScrutineePromotion}) {
     EqualityInfo<Type>? scrutineeInfo = scrutinee == null
         ? null
         : _computeEqualityInfo(scrutinee, scrutineeType);
     _stack.add(new _ScrutineeContext<Type>(
-        previousScrutineeReference: _scrutineeReference,
-        previousScrutineeSsaNode: _scrutineeSsaNode));
+        previousScrutineeReference: _scrutineeReference));
     ReferenceWithType<Type>? scrutineeReference = scrutineeInfo?._reference;
     _scrutineeReference = scrutineeReference;
-    _scrutineeSsaNode = scrutineeReference == null
-        ? new SsaNode<Type>(null)
-        : _current.infoFor(scrutineeReference.promotionKey).ssaNode;
+    SsaNode<Type>? scrutineeSsaNode;
+    if (!allowScrutineePromotion || scrutineeReference == null) {
+      scrutineeSsaNode = new SsaNode<Type>(null);
+    } else {
+      scrutineeSsaNode =
+          _current.infoFor(scrutineeReference.promotionKey).ssaNode;
+    }
     return new EqualityInfo._(
         scrutineeInfo?._expressionInfo,
         scrutineeType,
         new ReferenceWithType(
-            _makeTemporaryReference(_scrutineeSsaNode), scrutineeType,
+            _makeTemporaryReference(scrutineeSsaNode), scrutineeType,
             isPromotable: true, isThisOrSuper: false));
   }
 
@@ -5577,7 +5561,6 @@
   int declaredVariablePattern(
           {required Type matchedType,
           required Type staticType,
-          Expression? initializerExpression,
           bool isFinal = false,
           bool isLate = false,
           required bool isImplicitlyTyped}) =>
@@ -6248,16 +6231,11 @@
 class _ScrutineeContext<Type extends Object> extends _FlowContext {
   final ReferenceWithType<Type>? previousScrutineeReference;
 
-  final SsaNode<Type>? previousScrutineeSsaNode;
-
-  _ScrutineeContext(
-      {required this.previousScrutineeReference,
-      required this.previousScrutineeSsaNode});
+  _ScrutineeContext({required this.previousScrutineeReference});
 
   @override
   Map<String, Object?> get _debugFields => super._debugFields
-    ..['previousScrutineeReference'] = previousScrutineeReference
-    ..['previousScrutineeSsaNode'] = previousScrutineeSsaNode;
+    ..['previousScrutineeReference'] = previousScrutineeReference;
 
   @override
   String get _debugType => '_ScrutineeContext';
diff --git a/_fe_analyzer_shared/lib/src/macros/api/introspection.dart b/_fe_analyzer_shared/lib/src/macros/api/introspection.dart
index a9e6b74..21e2f57 100644
--- a/_fe_analyzer_shared/lib/src/macros/api/introspection.dart
+++ b/_fe_analyzer_shared/lib/src/macros/api/introspection.dart
@@ -117,18 +117,33 @@
 ///
 /// All type declarations which can have members will have a variant which
 /// implements this type.
-mixin IntrospectableType implements TypeDeclaration {}
+abstract class IntrospectableType implements TypeDeclaration {}
 
-/// Class (and enum) introspection information.
+/// Class introspection information.
 ///
 /// Information about fields, methods, and constructors must be retrieved from
 /// the `builder` objects.
 abstract class ClassDeclaration implements ParameterizedTypeDeclaration {
   /// Whether this class has an `abstract` modifier.
-  bool get isAbstract;
+  bool get hasAbstract;
+
+  /// Whether this class has a `base` modifier.
+  bool get hasBase;
 
   /// Whether this class has an `external` modifier.
-  bool get isExternal;
+  bool get hasExternal;
+
+  /// Whether this class has a `final` modifier.
+  bool get hasFinal;
+
+  /// Whether this class has an `interface` modifier.
+  bool get hasInterface;
+
+  /// Whether this class has a `mixin` modifier.
+  bool get hasMixin;
+
+  /// Whether this class has a `sealed` modifier.
+  bool get hasSealed;
 
   /// The `extends` type annotation, if present.
   NamedTypeAnnotation? get superclass;
@@ -138,10 +153,6 @@
 
   /// All the `with` type annotations.
   Iterable<NamedTypeAnnotation> get mixins;
-
-  /// All the type arguments, if applicable.
-  @override
-  Iterable<TypeParameterDeclaration> get typeParameters;
 }
 
 /// An introspectable class declaration.
diff --git a/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart b/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
index 8a2c936..e09bd06 100644
--- a/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
+++ b/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
@@ -572,10 +572,25 @@
   final List<NamedTypeAnnotationImpl> interfaces;
 
   @override
-  final bool isAbstract;
+  final bool hasAbstract;
 
   @override
-  final bool isExternal;
+  final bool hasBase;
+
+  @override
+  final bool hasExternal;
+
+  @override
+  final bool hasFinal;
+
+  @override
+  final bool hasInterface;
+
+  @override
+  final bool hasMixin;
+
+  @override
+  final bool hasSealed;
 
   @override
   final List<NamedTypeAnnotationImpl> mixins;
@@ -596,8 +611,13 @@
     required super.typeParameters,
     // ClassDeclaration fields
     required this.interfaces,
-    required this.isAbstract,
-    required this.isExternal,
+    required this.hasAbstract,
+    required this.hasBase,
+    required this.hasExternal,
+    required this.hasFinal,
+    required this.hasInterface,
+    required this.hasMixin,
+    required this.hasSealed,
     required this.mixins,
     required this.superclass,
   });
@@ -614,8 +634,13 @@
     }
     serializer
       ..endList()
-      ..addBool(isAbstract)
-      ..addBool(isExternal)
+      ..addBool(hasAbstract)
+      ..addBool(hasBase)
+      ..addBool(hasExternal)
+      ..addBool(hasFinal)
+      ..addBool(hasInterface)
+      ..addBool(hasMixin)
+      ..addBool(hasSealed)
       ..startList();
     for (NamedTypeAnnotationImpl mixin in mixins) {
       mixin.serialize(serializer);
diff --git a/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart b/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
index ca06efe..aa9d96a 100644
--- a/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
+++ b/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
@@ -220,8 +220,13 @@
         identifier: expectRemoteInstance(),
         typeParameters: (this..moveNext())._expectRemoteInstanceList(),
         interfaces: (this..moveNext())._expectRemoteInstanceList(),
-        isAbstract: (this..moveNext()).expectBool(),
-        isExternal: (this..moveNext()).expectBool(),
+        hasAbstract: (this..moveNext()).expectBool(),
+        hasBase: (this..moveNext()).expectBool(),
+        hasExternal: (this..moveNext()).expectBool(),
+        hasFinal: (this..moveNext()).expectBool(),
+        hasInterface: (this..moveNext()).expectBool(),
+        hasMixin: (this..moveNext()).expectBool(),
+        hasSealed: (this..moveNext()).expectBool(),
         mixins: (this..moveNext())._expectRemoteInstanceList(),
         superclass:
             (this..moveNext()).checkNull() ? null : expectRemoteInstance(),
@@ -234,8 +239,13 @@
         identifier: expectRemoteInstance(),
         typeParameters: (this..moveNext())._expectRemoteInstanceList(),
         interfaces: (this..moveNext())._expectRemoteInstanceList(),
-        isAbstract: (this..moveNext()).expectBool(),
-        isExternal: (this..moveNext()).expectBool(),
+        hasAbstract: (this..moveNext()).expectBool(),
+        hasBase: (this..moveNext()).expectBool(),
+        hasExternal: (this..moveNext()).expectBool(),
+        hasFinal: (this..moveNext()).expectBool(),
+        hasInterface: (this..moveNext()).expectBool(),
+        hasMixin: (this..moveNext()).expectBool(),
+        hasSealed: (this..moveNext()).expectBool(),
         mixins: (this..moveNext())._expectRemoteInstanceList(),
         superclass:
             (this..moveNext()).checkNull() ? null : expectRemoteInstance(),
diff --git a/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index 827b29e..5b07024 100644
--- a/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -3072,9 +3072,9 @@
 const MessageCode messageEmptyRecordTypeNamedFieldsList = const MessageCode(
     "EmptyRecordTypeNamedFieldsList",
     index: 129,
-    problemMessage: r"""Record type named fields list can't be empty.""",
-    correctionMessage:
-        r"""Try adding a record type named field to the list.""");
+    problemMessage:
+        r"""The list of named fields in a record type can't be empty.""",
+    correctionMessage: r"""Try adding a named field to the list.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeEncoding = messageEncoding;
@@ -3344,6 +3344,26 @@
         r"""The name 'values' is not a valid name for an enum. Try using a different name.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeEqualKeysInMapPattern = messageEqualKeysInMapPattern;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageEqualKeysInMapPattern = const MessageCode(
+    "EqualKeysInMapPattern",
+    analyzerCodes: <String>["EQUAL_KEYS_IN_MAP_PATTERN"],
+    problemMessage: r"""Two keys in a map pattern can't be equal.""",
+    correctionMessage: r"""Change or remove the duplicate key.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeEqualKeysInMapPatternContext =
+    messageEqualKeysInMapPatternContext;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageEqualKeysInMapPatternContext = const MessageCode(
+    "EqualKeysInMapPatternContext",
+    severity: Severity.context,
+    problemMessage: r"""This is the previous use of the same key.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeEqualityCannotBeEqualityOperand =
     messageEqualityCannotBeEqualityOperand;
 
@@ -4046,16 +4066,6 @@
         r"""This can't be used as an annotation; an annotation should be a reference to a compile-time constant variable, or a call to a constant constructor.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeExtendFunction = messageExtendFunction;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageExtendFunction = const MessageCode("ExtendFunction",
-    severity: Severity.ignored,
-    problemMessage: r"""Extending 'Function' is deprecated.""",
-    correctionMessage:
-        r"""Try removing 'Function' from the 'extends' clause.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<Message Function(String name)> templateExtendingEnum =
     const Template<Message Function(String name)>(
         problemMessageTemplate:
@@ -5297,6 +5307,15 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFinalMixinClass = messageFinalMixinClass;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFinalMixinClass = const MessageCode("FinalMixinClass",
+    index: 142,
+    problemMessage: r"""A mixin class can't be declared 'final'.""",
+    correctionMessage: r"""Try removing the 'final' keyword.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
         String
@@ -5654,17 +5673,6 @@
         r"""Functions marked 'sync*' can't have return type 'void'.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeImplementFunction = messageImplementFunction;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageImplementFunction = const MessageCode(
-    "ImplementFunction",
-    severity: Severity.ignored,
-    problemMessage: r"""Implementing 'Function' is deprecated.""",
-    correctionMessage:
-        r"""Try removing 'Function' from the 'implements' clause.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeImplementsBeforeExtends = messageImplementsBeforeExtends;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -6419,6 +6427,16 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeInterfaceMixinClass = messageInterfaceMixinClass;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageInterfaceMixinClass = const MessageCode(
+    "InterfaceMixinClass",
+    index: 143,
+    problemMessage: r"""A mixin class can't be declared 'interface'.""",
+    correctionMessage: r"""Try removing the 'interface' keyword.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
         String
@@ -8988,15 +9006,6 @@
     problemMessage: r"""Mixins can't declare constructors.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeMixinFunction = messageMixinFunction;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageMixinFunction = const MessageCode("MixinFunction",
-    severity: Severity.ignored,
-    problemMessage: r"""Mixing in 'Function' is deprecated.""",
-    correctionMessage: r"""Try removing 'Function' from the 'with' clause.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
         String
@@ -9267,6 +9276,26 @@
         r"""The default case is not reachable with sound null safety because the switch expression is non-nullable.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeNeverReachableSwitchExpressionError =
+    messageNeverReachableSwitchExpressionError;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageNeverReachableSwitchExpressionError = const MessageCode(
+    "NeverReachableSwitchExpressionError",
+    problemMessage:
+        r"""`null` encountered as case in a switch expression with a non-nullable type.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeNeverReachableSwitchStatementError =
+    messageNeverReachableSwitchStatementError;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageNeverReachableSwitchStatementError = const MessageCode(
+    "NeverReachableSwitchStatementError",
+    problemMessage:
+        r"""`null` encountered as case in a switch statement with a non-nullable type.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeNeverValueError = messageNeverValueError;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10120,6 +10149,17 @@
     problemMessage: r"""The class 'Object' can't use mixins.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeObsoleteColonForDefaultValue =
+    messageObsoleteColonForDefaultValue;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageObsoleteColonForDefaultValue = const MessageCode(
+    "ObsoleteColonForDefaultValue",
+    problemMessage:
+        r"""Using a colon as a separator before a default value is no longer supported.""",
+    correctionMessage: r"""Try replacing the colon with an equal sign.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeOnlyTry = messageOnlyTry;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10881,6 +10921,14 @@
     correctionMessage: r"""Try assigning to a local variable.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codePatternMatchingError = messagePatternMatchingError;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messagePatternMatchingError = const MessageCode(
+    "PatternMatchingError",
+    problemMessage: r"""Pattern matching error""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codePlatformPrivateLibraryAccess =
     messagePlatformPrivateLibraryAccess;
 
@@ -10963,7 +11011,7 @@
     const MessageCode("RecordLiteralOnePositionalFieldNoTrailingComma",
         index: 127,
         problemMessage:
-            r"""Record literal with one field requires a trailing comma.""",
+            r"""A record literal with exactly one positional field requires a trailing comma.""",
         correctionMessage: r"""Try adding a trailing comma.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10975,7 +11023,7 @@
     const MessageCode("RecordLiteralZeroFieldsWithTrailingComma",
         index: 128,
         problemMessage:
-            r"""Record literal without fields can't have a trailing comma.""",
+            r"""A record literal without fields can't have a trailing comma.""",
         correctionMessage: r"""Try removing the trailing comma.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10987,7 +11035,7 @@
     const MessageCode("RecordTypeOnePositionalFieldNoTrailingComma",
         index: 131,
         problemMessage:
-            r"""Record type with one entry requires a trailing comma.""",
+            r"""A record type with exactly one positional field requires a trailing comma.""",
         correctionMessage: r"""Try adding a trailing comma.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -10999,7 +11047,7 @@
     const MessageCode("RecordTypeZeroFieldsButTrailingComma",
         index: 130,
         problemMessage:
-            r"""Record type without fields can't have a trailing comma.""",
+            r"""A record type without fields can't have a trailing comma.""",
         correctionMessage: r"""Try removing the trailing comma.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -11307,6 +11355,16 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeSealedMixinClass = messageSealedMixinClass;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageSealedMixinClass = const MessageCode(
+    "SealedMixinClass",
+    index: 144,
+    problemMessage: r"""A mixin class can't be declared 'sealed'.""",
+    correctionMessage: r"""Try removing the 'sealed' keyword.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
         String
@@ -11639,6 +11697,76 @@
         r"""Loaded library is compiled with unsound null safety and cannot be used in compilation for sound null safety.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+    Message Function(
+        String name,
+        String
+            name2)> templateSubtypeOfBaseIsNotBaseFinalOrSealed = const Template<
+        Message Function(String name, String name2)>(
+    problemMessageTemplate:
+        r"""The type '#name' must be 'base', 'final' or 'sealed' because the supertype '#name2' is 'base'.""",
+    correctionMessageTemplate:
+        r"""Try adding 'base', 'final', or 'sealed' to the type.""",
+    withArguments: _withArgumentsSubtypeOfBaseIsNotBaseFinalOrSealed);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name, String name2)>
+    codeSubtypeOfBaseIsNotBaseFinalOrSealed =
+    const Code<Message Function(String name, String name2)>(
+        "SubtypeOfBaseIsNotBaseFinalOrSealed",
+        analyzerCodes: <String>["SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED"]);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsSubtypeOfBaseIsNotBaseFinalOrSealed(
+    String name, String name2) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  if (name2.isEmpty) throw 'No name provided';
+  name2 = demangleMixinApplicationName(name2);
+  return new Message(codeSubtypeOfBaseIsNotBaseFinalOrSealed,
+      problemMessage:
+          """The type '${name}' must be 'base', 'final' or 'sealed' because the supertype '${name2}' is 'base'.""",
+      correctionMessage: """Try adding 'base', 'final', or 'sealed' to the type.""",
+      arguments: {'name': name, 'name2': name2});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+    Message Function(
+        String name,
+        String
+            name2)> templateSubtypeOfFinalIsNotBaseFinalOrSealed = const Template<
+        Message Function(String name, String name2)>(
+    problemMessageTemplate:
+        r"""The type '#name' must be 'base', 'final' or 'sealed' because the supertype '#name2' is 'final'.""",
+    correctionMessageTemplate:
+        r"""Try adding 'base', 'final', or 'sealed' to the type.""",
+    withArguments: _withArgumentsSubtypeOfFinalIsNotBaseFinalOrSealed);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name, String name2)>
+    codeSubtypeOfFinalIsNotBaseFinalOrSealed =
+    const Code<Message Function(String name, String name2)>(
+        "SubtypeOfFinalIsNotBaseFinalOrSealed",
+        analyzerCodes: <String>[
+      "SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED"
+    ]);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsSubtypeOfFinalIsNotBaseFinalOrSealed(
+    String name, String name2) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  if (name2.isEmpty) throw 'No name provided';
+  name2 = demangleMixinApplicationName(name2);
+  return new Message(codeSubtypeOfFinalIsNotBaseFinalOrSealed,
+      problemMessage:
+          """The type '${name}' must be 'base', 'final' or 'sealed' because the supertype '${name2}' is 'final'.""",
+      correctionMessage: """Try adding 'base', 'final', or 'sealed' to the type.""",
+      arguments: {'name': name, 'name2': name2});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeSuperAsExpression = messageSuperAsExpression;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -12983,6 +13111,64 @@
     correctionMessage: r"""Try removing the type arguments.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceMismatchReturnAndArgumentTypes =
+    messageWeakReferenceMismatchReturnAndArgumentTypes;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceMismatchReturnAndArgumentTypes =
+    const MessageCode("WeakReferenceMismatchReturnAndArgumentTypes",
+        problemMessage:
+            r"""Return and argument types of a weak reference should match.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceNotOneArgument =
+    messageWeakReferenceNotOneArgument;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceNotOneArgument = const MessageCode(
+    "WeakReferenceNotOneArgument",
+    problemMessage:
+        r"""Weak reference should take one required positional argument.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceNotStatic = messageWeakReferenceNotStatic;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceNotStatic = const MessageCode(
+    "WeakReferenceNotStatic",
+    problemMessage:
+        r"""Weak reference pragma can be used on a static method only.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceReturnTypeNotNullable =
+    messageWeakReferenceReturnTypeNotNullable;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceReturnTypeNotNullable = const MessageCode(
+    "WeakReferenceReturnTypeNotNullable",
+    problemMessage: r"""Return type of a weak reference should be nullable.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceTargetHasParameters =
+    messageWeakReferenceTargetHasParameters;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceTargetHasParameters = const MessageCode(
+    "WeakReferenceTargetHasParameters",
+    problemMessage:
+        r"""The target of weak reference should not take parameters.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeWeakReferenceTargetNotStaticTearoff =
+    messageWeakReferenceTargetNotStaticTearoff;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageWeakReferenceTargetNotStaticTearoff = const MessageCode(
+    "WeakReferenceTargetNotStaticTearoff",
+    problemMessage:
+        r"""The target of weak reference should be a tearoff of a static method.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeWeakWithStrongDillLibrary =
     messageWeakWithStrongDillLibrary;
 
diff --git a/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart b/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
index b45c6a9..1bf48cb 100644
--- a/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
+++ b/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
@@ -2050,8 +2050,9 @@
   }
 
   @override
-  void handleValuedFormalParameter(Token equals, Token token) {
-    listener?.handleValuedFormalParameter(equals, token);
+  void handleValuedFormalParameter(
+      Token equals, Token token, FormalParameterKind kind) {
+    listener?.handleValuedFormalParameter(equals, token, kind);
   }
 
   @override
diff --git a/_fe_analyzer_shared/lib/src/parser/listener.dart b/_fe_analyzer_shared/lib/src/parser/listener.dart
index d21ca1b..fcafd3c 100644
--- a/_fe_analyzer_shared/lib/src/parser/listener.dart
+++ b/_fe_analyzer_shared/lib/src/parser/listener.dart
@@ -2073,7 +2073,8 @@
     logEvent("FormalParameterDefaultValueExpression");
   }
 
-  void handleValuedFormalParameter(Token equals, Token token) {
+  void handleValuedFormalParameter(
+      Token equals, Token token, FormalParameterKind kind) {
     logEvent("ValuedFormalParameter");
   }
 
diff --git a/_fe_analyzer_shared/lib/src/parser/parser_impl.dart b/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
index 8378c59..a2bf742 100644
--- a/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
+++ b/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
@@ -568,7 +568,7 @@
         optional('class', next.next!)) {
       macroToken = next;
       next = next.next!;
-    } else if (next.isIdentifier && next.lexeme == 'sealed') {
+    } else if (next.isIdentifier && optional('sealed', next)) {
       sealedToken = next;
       if (optional('class', next.next!) || optional('mixin', next.next!)) {
         next = next.next!;
@@ -579,12 +579,12 @@
         start = next;
         next = next.next!.next!;
       }
-    } else if (next.isIdentifier && next.lexeme == 'base') {
+    } else if (next.isIdentifier && optional('base', next)) {
       baseToken = next;
       if (optional('class', next.next!) || optional('mixin', next.next!)) {
         next = next.next!;
       }
-    } else if (next.isIdentifier && next.lexeme == 'interface') {
+    } else if (next.isIdentifier && optional('interface', next)) {
       interfaceToken = next;
       if (optional('class', next.next!) || optional('mixin', next.next!)) {
         next = next.next!;
@@ -712,8 +712,6 @@
           return parseTypedef(keyword);
         } else if (identical(value, 'mixin')) {
           if (identical(nextValue, 'class')) {
-            // TODO(kallentu): Error handling for any class modifier here other
-            // than base. Only base mixin classes are allowed.
             return _handleModifiersForClassDeclaration(
                 start,
                 keyword.next!,
@@ -721,7 +719,7 @@
                 inlineToken,
                 sealedToken,
                 baseToken,
-                /* interfaceToken = */ null,
+                interfaceToken,
                 keyword,
                 directiveState);
           }
@@ -767,6 +765,19 @@
     ModifierContext context = new ModifierContext(this);
     if (mixinToken != null) {
       context.parseClassModifiers(start, mixinToken);
+
+      // Mixin classes can't have any modifier other than a base modifier.
+      if (context.finalToken != null) {
+        reportRecoverableError(
+            context.finalToken!, codes.messageFinalMixinClass);
+      }
+      if (interfaceToken != null) {
+        reportRecoverableError(
+            interfaceToken, codes.messageInterfaceMixinClass);
+      }
+      if (sealedToken != null) {
+        reportRecoverableError(sealedToken, codes.messageSealedMixinClass);
+      }
     } else {
       context.parseClassModifiers(start, classKeyword);
     }
@@ -2086,7 +2097,7 @@
       listener.endFormalParameterDefaultValueExpression();
       // TODO(danrubel): Consider removing the last parameter from the
       // handleValuedFormalParameter event... it appears to be unused.
-      listener.handleValuedFormalParameter(equal, next);
+      listener.handleValuedFormalParameter(equal, next, parameterKind);
       if (parameterKind.isRequiredPositional) {
         reportRecoverableError(
             equal, codes.messageRequiredParameterWithDefault);
@@ -2544,7 +2555,16 @@
       Token? mixinToken,
       Token classKeyword) {
     assert(optional('class', classKeyword));
-    Token begin = abstractToken ?? classKeyword;
+    Token begin = abstractToken ??
+        macroToken ??
+        inlineToken ??
+        sealedToken ??
+        baseToken ??
+        interfaceToken ??
+        finalToken ??
+        augmentToken ??
+        mixinToken ??
+        classKeyword;
     listener.beginClassOrMixinOrNamedMixinApplicationPrelude(begin);
     Token name = ensureIdentifier(
         classKeyword, IdentifierContext.classOrMixinOrExtensionDeclaration);
diff --git a/_fe_analyzer_shared/lib/src/scanner/token.dart b/_fe_analyzer_shared/lib/src/scanner/token.dart
index 5d8009b..b92d14f 100644
--- a/_fe_analyzer_shared/lib/src/scanner/token.dart
+++ b/_fe_analyzer_shared/lib/src/scanner/token.dart
@@ -125,218 +125,224 @@
   static const Keyword AWAIT =
       const Keyword(/* index = */ 84, "await", "AWAIT", KeywordStyle.pseudo);
 
+  static const Keyword BASE =
+      const Keyword(/* index = */ 85, "base", "BASE", KeywordStyle.pseudo);
+
   static const Keyword BREAK =
-      const Keyword(/* index = */ 85, "break", "BREAK", KeywordStyle.reserved);
+      const Keyword(/* index = */ 86, "break", "BREAK", KeywordStyle.reserved);
 
   static const Keyword CASE =
-      const Keyword(/* index = */ 86, "case", "CASE", KeywordStyle.reserved);
+      const Keyword(/* index = */ 87, "case", "CASE", KeywordStyle.reserved);
 
   static const Keyword CATCH =
-      const Keyword(/* index = */ 87, "catch", "CATCH", KeywordStyle.reserved);
+      const Keyword(/* index = */ 88, "catch", "CATCH", KeywordStyle.reserved);
 
   static const Keyword CLASS = const Keyword(
-      /* index = */ 88, "class", "CLASS", KeywordStyle.reserved,
+      /* index = */ 89, "class", "CLASS", KeywordStyle.reserved,
       isTopLevelKeyword: true);
 
   static const Keyword CONST = const Keyword(
-      /* index = */ 89, "const", "CONST", KeywordStyle.reserved,
+      /* index = */ 90, "const", "CONST", KeywordStyle.reserved,
       isModifier: true);
 
   static const Keyword CONTINUE = const Keyword(
-      /* index = */ 90, "continue", "CONTINUE", KeywordStyle.reserved);
+      /* index = */ 91, "continue", "CONTINUE", KeywordStyle.reserved);
 
   static const Keyword COVARIANT = const Keyword(
-      /* index = */ 91, "covariant", "COVARIANT", KeywordStyle.builtIn,
+      /* index = */ 92, "covariant", "COVARIANT", KeywordStyle.builtIn,
       isModifier: true);
 
   static const Keyword DEFAULT = const Keyword(
-      /* index = */ 92, "default", "DEFAULT", KeywordStyle.reserved);
+      /* index = */ 93, "default", "DEFAULT", KeywordStyle.reserved);
 
   static const Keyword DEFERRED = const Keyword(
-      /* index = */ 93, "deferred", "DEFERRED", KeywordStyle.builtIn);
+      /* index = */ 94, "deferred", "DEFERRED", KeywordStyle.builtIn);
 
   static const Keyword DO =
-      const Keyword(/* index = */ 94, "do", "DO", KeywordStyle.reserved);
+      const Keyword(/* index = */ 95, "do", "DO", KeywordStyle.reserved);
 
   static const Keyword DYNAMIC = const Keyword(
-      /* index = */ 95, "dynamic", "DYNAMIC", KeywordStyle.builtIn);
+      /* index = */ 96, "dynamic", "DYNAMIC", KeywordStyle.builtIn);
 
   static const Keyword ELSE =
-      const Keyword(/* index = */ 96, "else", "ELSE", KeywordStyle.reserved);
+      const Keyword(/* index = */ 97, "else", "ELSE", KeywordStyle.reserved);
 
   static const Keyword ENUM = const Keyword(
-      /* index = */ 97, "enum", "ENUM", KeywordStyle.reserved,
+      /* index = */ 98, "enum", "ENUM", KeywordStyle.reserved,
       isTopLevelKeyword: true);
 
   static const Keyword EXPORT = const Keyword(
-      /* index = */ 98, "export", "EXPORT", KeywordStyle.builtIn,
+      /* index = */ 99, "export", "EXPORT", KeywordStyle.builtIn,
       isTopLevelKeyword: true);
 
   static const Keyword EXTENDS = const Keyword(
-      /* index = */ 99, "extends", "EXTENDS", KeywordStyle.reserved);
+      /* index = */ 100, "extends", "EXTENDS", KeywordStyle.reserved);
 
   static const Keyword EXTENSION = const Keyword(
-      /* index = */ 100, "extension", "EXTENSION", KeywordStyle.builtIn,
+      /* index = */ 101, "extension", "EXTENSION", KeywordStyle.builtIn,
       isTopLevelKeyword: true);
 
   static const Keyword EXTERNAL = const Keyword(
-      /* index = */ 101, "external", "EXTERNAL", KeywordStyle.builtIn,
+      /* index = */ 102, "external", "EXTERNAL", KeywordStyle.builtIn,
       isModifier: true);
 
   static const Keyword FACTORY = const Keyword(
-      /* index = */ 102, "factory", "FACTORY", KeywordStyle.builtIn);
+      /* index = */ 103, "factory", "FACTORY", KeywordStyle.builtIn);
 
   static const Keyword FALSE =
-      const Keyword(/* index = */ 103, "false", "FALSE", KeywordStyle.reserved);
+      const Keyword(/* index = */ 104, "false", "FALSE", KeywordStyle.reserved);
 
   static const Keyword FINAL = const Keyword(
-      /* index = */ 104, "final", "FINAL", KeywordStyle.reserved,
+      /* index = */ 105, "final", "FINAL", KeywordStyle.reserved,
       isModifier: true);
 
   static const Keyword FINALLY = const Keyword(
-      /* index = */ 105, "finally", "FINALLY", KeywordStyle.reserved);
+      /* index = */ 106, "finally", "FINALLY", KeywordStyle.reserved);
 
   static const Keyword FOR =
-      const Keyword(/* index = */ 106, "for", "FOR", KeywordStyle.reserved);
+      const Keyword(/* index = */ 107, "for", "FOR", KeywordStyle.reserved);
 
   static const Keyword FUNCTION = const Keyword(
-      /* index = */ 107, "Function", "FUNCTION", KeywordStyle.builtIn);
+      /* index = */ 108, "Function", "FUNCTION", KeywordStyle.builtIn);
 
   static const Keyword GET =
-      const Keyword(/* index = */ 108, "get", "GET", KeywordStyle.builtIn);
+      const Keyword(/* index = */ 109, "get", "GET", KeywordStyle.builtIn);
 
   static const Keyword HIDE =
-      const Keyword(/* index = */ 109, "hide", "HIDE", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 110, "hide", "HIDE", KeywordStyle.pseudo);
 
   static const Keyword IF =
-      const Keyword(/* index = */ 110, "if", "IF", KeywordStyle.reserved);
+      const Keyword(/* index = */ 111, "if", "IF", KeywordStyle.reserved);
 
   static const Keyword IMPLEMENTS = const Keyword(
-      /* index = */ 111, "implements", "IMPLEMENTS", KeywordStyle.builtIn);
+      /* index = */ 112, "implements", "IMPLEMENTS", KeywordStyle.builtIn);
 
   static const Keyword IMPORT = const Keyword(
-      /* index = */ 112, "import", "IMPORT", KeywordStyle.builtIn,
+      /* index = */ 113, "import", "IMPORT", KeywordStyle.builtIn,
       isTopLevelKeyword: true);
 
   static const Keyword IN =
-      const Keyword(/* index = */ 113, "in", "IN", KeywordStyle.reserved);
+      const Keyword(/* index = */ 114, "in", "IN", KeywordStyle.reserved);
 
   static const Keyword INOUT =
-      const Keyword(/* index = */ 114, "inout", "INOUT", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 115, "inout", "INOUT", KeywordStyle.pseudo);
 
   static const Keyword INTERFACE = const Keyword(
-      /* index = */ 115, "interface", "INTERFACE", KeywordStyle.builtIn);
+      /* index = */ 116, "interface", "INTERFACE", KeywordStyle.builtIn);
 
   static const Keyword IS = const Keyword(
-      /* index = */ 116, "is", "IS", KeywordStyle.reserved,
+      /* index = */ 117, "is", "IS", KeywordStyle.reserved,
       precedence: RELATIONAL_PRECEDENCE);
 
   static const Keyword LATE = const Keyword(
-      /* index = */ 117, "late", "LATE", KeywordStyle.builtIn,
+      /* index = */ 118, "late", "LATE", KeywordStyle.builtIn,
       isModifier: true);
 
   static const Keyword LIBRARY = const Keyword(
-      /* index = */ 118, "library", "LIBRARY", KeywordStyle.builtIn,
+      /* index = */ 119, "library", "LIBRARY", KeywordStyle.builtIn,
       isTopLevelKeyword: true);
 
   static const Keyword MIXIN = const Keyword(
-      /* index = */ 119, "mixin", "MIXIN", KeywordStyle.builtIn,
+      /* index = */ 120, "mixin", "MIXIN", KeywordStyle.builtIn,
       isTopLevelKeyword: true);
 
   static const Keyword NATIVE =
-      const Keyword(/* index = */ 120, "native", "NATIVE", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 121, "native", "NATIVE", KeywordStyle.pseudo);
 
   static const Keyword NEW =
-      const Keyword(/* index = */ 121, "new", "NEW", KeywordStyle.reserved);
+      const Keyword(/* index = */ 122, "new", "NEW", KeywordStyle.reserved);
 
   static const Keyword NULL =
-      const Keyword(/* index = */ 122, "null", "NULL", KeywordStyle.reserved);
+      const Keyword(/* index = */ 123, "null", "NULL", KeywordStyle.reserved);
 
   static const Keyword OF =
-      const Keyword(/* index = */ 123, "of", "OF", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 124, "of", "OF", KeywordStyle.pseudo);
 
   static const Keyword ON =
-      const Keyword(/* index = */ 124, "on", "ON", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 125, "on", "ON", KeywordStyle.pseudo);
 
   static const Keyword OPERATOR = const Keyword(
-      /* index = */ 125, "operator", "OPERATOR", KeywordStyle.builtIn);
+      /* index = */ 126, "operator", "OPERATOR", KeywordStyle.builtIn);
 
   static const Keyword OUT =
-      const Keyword(/* index = */ 126, "out", "OUT", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 127, "out", "OUT", KeywordStyle.pseudo);
 
   static const Keyword PART = const Keyword(
-      /* index = */ 127, "part", "PART", KeywordStyle.builtIn,
+      /* index = */ 128, "part", "PART", KeywordStyle.builtIn,
       isTopLevelKeyword: true);
 
   static const Keyword PATCH =
-      const Keyword(/* index = */ 128, "patch", "PATCH", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 129, "patch", "PATCH", KeywordStyle.pseudo);
 
   static const Keyword REQUIRED = const Keyword(
-      /* index = */ 129, "required", "REQUIRED", KeywordStyle.builtIn,
+      /* index = */ 130, "required", "REQUIRED", KeywordStyle.builtIn,
       isModifier: true);
 
   static const Keyword RETHROW = const Keyword(
-      /* index = */ 130, "rethrow", "RETHROW", KeywordStyle.reserved);
+      /* index = */ 131, "rethrow", "RETHROW", KeywordStyle.reserved);
 
   static const Keyword RETURN = const Keyword(
-      /* index = */ 131, "return", "RETURN", KeywordStyle.reserved);
+      /* index = */ 132, "return", "RETURN", KeywordStyle.reserved);
+
+  static const Keyword SEALED =
+      const Keyword(/* index = */ 133, "sealed", "SEALED", KeywordStyle.pseudo);
 
   static const Keyword SET =
-      const Keyword(/* index = */ 132, "set", "SET", KeywordStyle.builtIn);
+      const Keyword(/* index = */ 134, "set", "SET", KeywordStyle.builtIn);
 
   static const Keyword SHOW =
-      const Keyword(/* index = */ 133, "show", "SHOW", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 135, "show", "SHOW", KeywordStyle.pseudo);
 
   static const Keyword SOURCE =
-      const Keyword(/* index = */ 134, "source", "SOURCE", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 136, "source", "SOURCE", KeywordStyle.pseudo);
 
   static const Keyword STATIC = const Keyword(
-      /* index = */ 135, "static", "STATIC", KeywordStyle.builtIn,
+      /* index = */ 137, "static", "STATIC", KeywordStyle.builtIn,
       isModifier: true);
 
   static const Keyword SUPER =
-      const Keyword(/* index = */ 136, "super", "SUPER", KeywordStyle.reserved);
+      const Keyword(/* index = */ 138, "super", "SUPER", KeywordStyle.reserved);
 
   static const Keyword SWITCH = const Keyword(
-      /* index = */ 137, "switch", "SWITCH", KeywordStyle.reserved);
+      /* index = */ 139, "switch", "SWITCH", KeywordStyle.reserved);
 
   static const Keyword SYNC =
-      const Keyword(/* index = */ 138, "sync", "SYNC", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 140, "sync", "SYNC", KeywordStyle.pseudo);
 
   static const Keyword THIS =
-      const Keyword(/* index = */ 139, "this", "THIS", KeywordStyle.reserved);
+      const Keyword(/* index = */ 141, "this", "THIS", KeywordStyle.reserved);
 
   static const Keyword THROW =
-      const Keyword(/* index = */ 140, "throw", "THROW", KeywordStyle.reserved);
+      const Keyword(/* index = */ 142, "throw", "THROW", KeywordStyle.reserved);
 
   static const Keyword TRUE =
-      const Keyword(/* index = */ 141, "true", "TRUE", KeywordStyle.reserved);
+      const Keyword(/* index = */ 143, "true", "TRUE", KeywordStyle.reserved);
 
   static const Keyword TRY =
-      const Keyword(/* index = */ 142, "try", "TRY", KeywordStyle.reserved);
+      const Keyword(/* index = */ 144, "try", "TRY", KeywordStyle.reserved);
 
   static const Keyword TYPEDEF = const Keyword(
-      /* index = */ 143, "typedef", "TYPEDEF", KeywordStyle.builtIn,
+      /* index = */ 145, "typedef", "TYPEDEF", KeywordStyle.builtIn,
       isTopLevelKeyword: true);
 
   static const Keyword VAR = const Keyword(
-      /* index = */ 144, "var", "VAR", KeywordStyle.reserved,
+      /* index = */ 146, "var", "VAR", KeywordStyle.reserved,
       isModifier: true);
 
   static const Keyword VOID =
-      const Keyword(/* index = */ 145, "void", "VOID", KeywordStyle.reserved);
+      const Keyword(/* index = */ 147, "void", "VOID", KeywordStyle.reserved);
 
   static const Keyword WHEN =
-      const Keyword(/* index = */ 146, "when", 'WHEN', KeywordStyle.pseudo);
+      const Keyword(/* index = */ 148, "when", 'WHEN', KeywordStyle.pseudo);
 
   static const Keyword WHILE =
-      const Keyword(/* index = */ 147, "while", "WHILE", KeywordStyle.reserved);
+      const Keyword(/* index = */ 149, "while", "WHILE", KeywordStyle.reserved);
 
   static const Keyword WITH =
-      const Keyword(/* index = */ 148, "with", "WITH", KeywordStyle.reserved);
+      const Keyword(/* index = */ 150, "with", "WITH", KeywordStyle.reserved);
 
   static const Keyword YIELD =
-      const Keyword(/* index = */ 149, "yield", "YIELD", KeywordStyle.pseudo);
+      const Keyword(/* index = */ 151, "yield", "YIELD", KeywordStyle.pseudo);
 
   static const List<Keyword> values = const <Keyword>[
     ABSTRACT,
@@ -345,6 +351,7 @@
     ASYNC,
     AUGMENT,
     AWAIT,
+    BASE,
     BREAK,
     CASE,
     CATCH,
@@ -392,6 +399,7 @@
     REQUIRED,
     RETHROW,
     RETURN,
+    SEALED,
     SET,
     SHOW,
     SOURCE,
@@ -1921,6 +1929,7 @@
   Keyword.ASYNC,
   Keyword.AUGMENT,
   Keyword.AWAIT,
+  Keyword.BASE,
   Keyword.BREAK,
   Keyword.CASE,
   Keyword.CATCH,
@@ -1968,6 +1977,7 @@
   Keyword.REQUIRED,
   Keyword.RETHROW,
   Keyword.RETURN,
+  Keyword.SEALED,
   Keyword.SET,
   Keyword.SHOW,
   Keyword.SOURCE,
diff --git a/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart b/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
index c55dd3c..989b2a1 100644
--- a/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
+++ b/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
@@ -51,12 +51,6 @@
   /// Indicates whether variables declared in the pattern should be `late`.
   final bool isLate;
 
-  /// The initializer being assigned to this pattern via a variable declaration
-  /// statement, or `null` if this pattern does not occur in a variable
-  /// declaration statement, or this pattern is not the top-level pattern in
-  /// the declaration.
-  final Expression? initializer;
-
   /// The switch scrutinee, or `null` if this pattern does not occur in a switch
   /// statement or switch expression, or this pattern is not the top-level
   /// pattern.
@@ -79,7 +73,6 @@
   final UnnecessaryWildcardKind? unnecessaryWildcardKind;
 
   MatchContext({
-    this.initializer,
     this.irrefutableContext,
     required this.isFinal,
     this.isLate = false,
@@ -97,7 +90,6 @@
       irrefutableContext == null
           ? this
           : new MatchContext(
-              initializer: initializer,
               isFinal: isFinal,
               isLate: isLate,
               switchScrutinee: switchScrutinee,
@@ -111,7 +103,6 @@
   MatchContext<Node, Expression, Pattern, Type, Variable> withPromotionKeys(
           Map<String, int> patternVariablePromotionKeys) =>
       new MatchContext(
-        initializer: null,
         irrefutableContext: irrefutableContext,
         isFinal: isFinal,
         isLate: isLate,
@@ -129,7 +120,6 @@
       withUnnecessaryWildcardKind(
           UnnecessaryWildcardKind? unnecessaryWildcardKind) {
     return new MatchContext(
-      initializer: null,
       irrefutableContext: irrefutableContext,
       isFinal: isFinal,
       isLate: isLate,
@@ -170,8 +160,32 @@
   Type resolveShorting() => type;
 }
 
+/// Result for analyzing a switch expression in
+/// [TypeAnalyzer.analyzeSwitchExpression].
+class SwitchExpressionResult<Type extends Object, Error>
+    extends SimpleTypeAnalysisResult<Type> {
+  /// Errors for non-bool guards.
+  ///
+  /// The key is the case index of the erroneous guard.
+  ///
+  /// This is `null` if no such errors where found.
+  final Map<int, Error>? nonBooleanGuardErrors;
+
+  /// The types of the guard expressions.
+  ///
+  /// The key is the case index of the guard.
+  ///
+  /// This is `null` if no such guards where present.
+  final Map<int, Type>? guardTypes;
+
+  SwitchExpressionResult(
+      {required super.type,
+      required this.nonBooleanGuardErrors,
+      required this.guardTypes});
+}
+
 /// Container for the result of running type analysis on an integer literal.
-class SwitchStatementTypeAnalysisResult<Type> {
+class SwitchStatementTypeAnalysisResult<Type extends Object, Error> {
   /// Whether the switch statement had a `default` clause.
   final bool hasDefault;
 
@@ -192,12 +206,34 @@
   /// The static type of the scrutinee expression.
   final Type scrutineeType;
 
+  /// Errors for the cases that don't complete normally.
+  ///
+  /// This is `null` if no such errors where found.
+  final Map<int, Error>? switchCaseCompletesNormallyErrors;
+
+  /// Errors for non-bool guards.
+  ///
+  /// The keys of the maps are case and head indices of the erroneous guard.
+  ///
+  /// This is `null` if no such errors where found.
+  final Map<int, Map<int, Error>>? nonBooleanGuardErrors;
+
+  /// The types of the guard expressions.
+  ///
+  /// The keys of the maps are case and head indices of the guard.
+  ///
+  /// This is `null` if no such guards where present.
+  final Map<int, Map<int, Type>>? guardTypes;
+
   SwitchStatementTypeAnalysisResult({
     required this.hasDefault,
     required this.isExhaustive,
     required this.lastCaseTerminates,
     required this.requiresExhaustivenessValidation,
     required this.scrutineeType,
+    required this.switchCaseCompletesNormallyErrors,
+    required this.nonBooleanGuardErrors,
+    required this.guardTypes,
   });
 }
 
@@ -212,3 +248,230 @@
   /// and can be removed.
   logicalAndPatternOperand,
 }
+
+/// Result for analyzing an assigned variable pattern in
+/// [TypeAnalyzer.analyzeAssignedVariablePattern].
+class AssignedVariablePatternResult<Error> {
+  /// Error for when a variable was assigned multiple times within a pattern.
+  final Error? duplicateAssignmentPatternVariableError;
+
+  /// Error for when the matched value type is not assignable to the variable
+  /// type in an irrefutable context.
+  final Error? patternTypeMismatchInIrrefutableContextError;
+
+  AssignedVariablePatternResult(
+      {required this.duplicateAssignmentPatternVariableError,
+      required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing an object pattern in
+/// [TypeAnalyzer.analyzeObjectPattern].
+class ObjectPatternResult<Type extends Object, Error> {
+  /// The required type of the object pattern.
+  final Type requiredType;
+
+  /// Errors for when the same property name was used multiple times in the
+  /// object pattern.
+  ///
+  /// The key is the index of the duplicate field within the object pattern.
+  ///
+  /// This is `null` if no such properties were found.
+  final Map<int, Error>? duplicateRecordPatternFieldErrors;
+
+  /// Error for when the matched value type is not assignable to the required
+  /// type in an irrefutable context.
+  final Error? patternTypeMismatchInIrrefutableContextError;
+
+  ObjectPatternResult(
+      {required this.requiredType,
+      required this.duplicateRecordPatternFieldErrors,
+      required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a record pattern in
+/// [TypeAnalyzer.analyzeRecordPattern].
+class RecordPatternResult<Type extends Object, Error> {
+  /// The required type of the record pattern.
+  final Type requiredType;
+
+  /// Errors for when the same property name was used multiple times in the
+  /// record pattern.
+  ///
+  /// The key is the index of the duplicate field within the record pattern.
+  ///
+  /// This is `null` if no such errors where found.
+  final Map<int, Error>? duplicateRecordPatternFieldErrors;
+
+  /// Error for when the matched value type is not assignable to the required
+  /// type in an irrefutable context.
+  final Error? patternTypeMismatchInIrrefutableContextError;
+
+  RecordPatternResult(
+      {required this.requiredType,
+      required this.duplicateRecordPatternFieldErrors,
+      required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a list pattern in [TypeAnalyzer.analyzeListPattern].
+class ListPatternResult<Type extends Object, Error> {
+  /// The required type of the list pattern.
+  final Type requiredType;
+
+  /// Errors for when multiple rest patterns occurred within the list pattern.
+  ///
+  /// The key is the index of the pattern within the list pattern.
+  ///
+  /// This is `null` if no such errors where found.
+  final Map<int, Error>? duplicateRestPatternErrors;
+
+  /// Error for when the matched value type is not assignable to the required
+  /// type in an irrefutable context.
+  final Error? patternTypeMismatchInIrrefutableContextError;
+
+  ListPatternResult(
+      {required this.requiredType,
+      required this.duplicateRestPatternErrors,
+      required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a map pattern in [TypeAnalyzer.analyzeMapPattern].
+class MapPatternResult<Type extends Object, Error> {
+  /// The required type of the map pattern.
+  final Type requiredType;
+
+  /// Errors for when multiple rest patterns occurred within the map pattern.
+  ///
+  /// The key is the index of the pattern within the map pattern.
+  ///
+  /// This is `null` if no such errors where found.
+  final Map<int, Error>? duplicateRestPatternErrors;
+
+  /// Error for when the matched value type is not assignable to the required
+  /// type in an irrefutable context.
+  final Error? patternTypeMismatchInIrrefutableContextError;
+
+  MapPatternResult(
+      {required this.requiredType,
+      required this.duplicateRestPatternErrors,
+      required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a constant pattern in
+/// [TypeAnalyzer.analyzeConstantPattern].
+class ConstantPatternResult<Type extends Object, Error> {
+  /// The static type of the constant expression.
+  final Type expressionType;
+
+  /// Error for when the pattern occurred in an irrefutable context.
+  final Error? refutablePatternInIrrefutableContextError;
+
+  /// Error for when the pattern, used as a case constant expression, does not
+  /// have a valid type wrt. the switch expression type.
+  final Error? caseExpressionTypeMismatchError;
+
+  ConstantPatternResult(
+      {required this.expressionType,
+      required this.refutablePatternInIrrefutableContextError,
+      required this.caseExpressionTypeMismatchError});
+}
+
+/// Result for analyzing a declared variable pattern in
+/// [TypeAnalyzer.analyzeDeclaredVariablePattern].
+class DeclaredVariablePatternResult<Type extends Object, Error> {
+  /// The static type of the variable.
+  final Type staticType;
+
+  /// Error for when the matched value type is not assignable to the static
+  /// type in an irrefutable context.
+  final Error? patternTypeMismatchInIrrefutableContextError;
+
+  DeclaredVariablePatternResult(
+      {required this.staticType,
+      required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing a logical or pattern in
+/// [TypeAnalyzer.analyzeLogicalOrPattern].
+class LogicalOrPatternResult<Error> {
+  /// Error for when the pattern occurred in an irrefutable context.
+  final Error? refutablePatternInIrrefutableContextError;
+
+  LogicalOrPatternResult(
+      {required this.refutablePatternInIrrefutableContextError});
+}
+
+/// Result for analyzing a relational pattern in
+/// [TypeAnalyzer.analyzeRelationalPattern].
+class RelationalPatternResult<Type extends Object, Error> {
+  /// The static type of the operand.
+  final Type operandType;
+
+  /// Error for when the pattern occurred in an irrefutable context.
+  final Error? refutablePatternInIrrefutableContextError;
+
+  /// Error for when the operand type is not assignable to the parameter type
+  /// of the relational operator.
+  final Error? argumentTypeNotAssignableError;
+
+  /// Error for when the relational operator does not return a bool.
+  final Error? operatorReturnTypeNotAssignableToBoolError;
+
+  RelationalPatternResult(
+      {required this.operandType,
+      required this.refutablePatternInIrrefutableContextError,
+      required this.argumentTypeNotAssignableError,
+      required this.operatorReturnTypeNotAssignableToBoolError});
+}
+
+/// Result for analyzing a null check or null assert pattern in
+/// [TypeAnalyzer.analyzeNullCheckOrAssertPattern].
+class NullCheckOrAssertPatternResult<Error> {
+  /// Error for when the pattern occurred in an irrefutable context.
+  final Error? refutablePatternInIrrefutableContextError;
+
+  /// Error for when the matched type is known to be non-null.
+  final Error? matchedTypeIsStrictlyNonNullableError;
+
+  NullCheckOrAssertPatternResult(
+      {required this.refutablePatternInIrrefutableContextError,
+      required this.matchedTypeIsStrictlyNonNullableError});
+}
+
+/// Result for analyzing a wildcard pattern
+/// [TypeAnalyzer.analyzeWildcardPattern].
+class WildcardPatternResult<Error> {
+  /// Error for when the matched value type is not assignable to the wildcard
+  /// type in an irrefutable context.
+  final Error? patternTypeMismatchInIrrefutableContextError;
+
+  WildcardPatternResult(
+      {required this.patternTypeMismatchInIrrefutableContextError});
+}
+
+/// Result for analyzing an if-case statement or element in
+/// [TypeAnalyzer.analyzeIfCaseStatement] and
+/// [TypeAnalyzer.analyzeIfCaseElement].
+class IfCaseStatementResult<Type extends Object, Error> {
+  /// The static type of the matched expression.
+  final Type matchedExpressionType;
+
+  /// Error for when the guard has a non-bool type.
+  final Error? nonBooleanGuardError;
+
+  /// The type of the guard expression, if present.
+  final Type? guardType;
+
+  IfCaseStatementResult(
+      {required this.matchedExpressionType,
+      required this.nonBooleanGuardError,
+      required this.guardType});
+}
+
+/// Result for analyzing a pattern-for-in statement or element in
+/// [TypeAnalyzer.analyzePatternForIn].
+class PatternForInResult<Error> {
+  /// Error for when the expression is not an iterable.
+  final Error? patternForInExpressionIsNotIterableError;
+
+  PatternForInResult({required this.patternForInExpressionIsNotIterableError});
+}
diff --git a/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart b/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
index f0a7fce..c236f49 100644
--- a/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
+++ b/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
@@ -33,6 +33,47 @@
   });
 }
 
+/// The kind of inconsistency identified for a variable.
+enum JoinedPatternVariableInconsistency {
+  /// No inconsistency.
+  none(0),
+
+  /// Only one branch of a logical-or pattern has the variable.
+  logicalOr(4),
+
+  /// Not every case of a shared case scope has the variable.
+  sharedCaseAbsent(3),
+
+  /// The shared case scope has a label or `default` case.
+  sharedCaseHasLabel(2),
+
+  /// The finality or type of the variable components is not the same.
+  /// This is reported for both logical-or and shared cases.
+  differentFinalityOrType(1);
+
+  final int _severity;
+
+  const JoinedPatternVariableInconsistency(this._severity);
+
+  /// Returns the most serious inconsistency for `this` or [other].
+  JoinedPatternVariableInconsistency maxWith(
+    JoinedPatternVariableInconsistency other,
+  ) {
+    return _severity > other._severity ? this : other;
+  }
+
+  /// Returns the most serious inconsistency for `this` or [others].
+  JoinedPatternVariableInconsistency maxWithAll(
+    Iterable<JoinedPatternVariableInconsistency> others,
+  ) {
+    JoinedPatternVariableInconsistency result = this;
+    for (JoinedPatternVariableInconsistency other in others) {
+      result = result.maxWith(other);
+    }
+    return result;
+  }
+}
+
 /// The location where the join of a pattern variable happens.
 enum JoinedPatternVariableLocation {
   /// A single pattern, from `logical-or` patterns.
@@ -161,8 +202,11 @@
   /// might become not consistent.
   final Map<String, Variable> variables;
 
-  SwitchStatementMemberInfo(this.heads, this.body, this.variables,
-      {required this.hasLabels});
+  SwitchStatementMemberInfo(
+      {required this.heads,
+      required this.body,
+      required this.variables,
+      required this.hasLabels});
 }
 
 /// Type analysis logic to be shared between the analyzer and front end.  The
@@ -238,7 +282,8 @@
     Expression extends Node,
     Variable extends Object,
     Type extends Object,
-    Pattern extends Node> {
+    Pattern extends Node,
+    Error> {
   /// Returns the type `bool`.
   Type get boolType;
 
@@ -248,8 +293,8 @@
   /// Returns the type `dynamic`.
   Type get dynamicType;
 
-  TypeAnalyzerErrors<Node, Statement, Expression, Variable, Type, Pattern>?
-      get errors;
+  TypeAnalyzerErrors<Node, Statement, Expression, Variable, Type, Pattern,
+      Error> get errors;
 
   /// Returns the type used by the client in the case of errors.
   Type get errorType;
@@ -277,23 +322,28 @@
   /// context.  [node] is the pattern itself, and [variable] is the variable
   /// being referenced.
   ///
+  /// Returns an [AssignedVariablePatternResult] with information about reported
+  /// errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// For wildcard patterns in an assignment context,
   /// [analyzeDeclaredVariablePattern] should be used instead.
   ///
   /// Stack effect: none.
-  void analyzeAssignedVariablePattern(
+  AssignedVariablePatternResult<Error> analyzeAssignedVariablePattern(
       MatchContext<Node, Expression, Pattern, Type, Variable> context,
       Pattern node,
       Variable variable) {
+    Error? duplicateAssignmentPatternVariableError;
     Map<Variable, Pattern>? assignedVariables = context.assignedVariables;
     if (assignedVariables != null) {
       Pattern? original = assignedVariables[variable];
       if (original == null) {
         assignedVariables[variable] = node;
       } else {
-        errors?.duplicateAssignmentPatternVariable(
+        duplicateAssignmentPatternVariableError =
+            errors.duplicateAssignmentPatternVariable(
           variable: variable,
           original: original,
           duplicate: node,
@@ -306,17 +356,25 @@
     assert(irrefutableContext != null,
         'Assigned variables must only appear in irrefutable pattern contexts');
     Type matchedType = flow.getMatchedValueType();
+    Error? patternTypeMismatchInIrrefutableContextError;
     if (irrefutableContext != null &&
-        !operations.isAssignableTo(matchedType, variableDeclaredType)) {
-      errors?.patternTypeMismatchInIrrefutableContext(
-          pattern: node,
-          context: irrefutableContext,
-          matchedType: matchedType,
-          requiredType: variableDeclaredType);
+        !operations.isDynamic(matchedType) &&
+        !operations.isSubtypeOf(matchedType, variableDeclaredType)) {
+      patternTypeMismatchInIrrefutableContextError =
+          errors.patternTypeMismatchInIrrefutableContext(
+              pattern: node,
+              context: irrefutableContext,
+              matchedType: matchedType,
+              requiredType: variableDeclaredType);
     }
     flow.promoteForPattern(
         matchedType: matchedType, knownType: variableDeclaredType);
     flow.assignedVariablePattern(node, variable, matchedType);
+    return new AssignedVariablePatternResult(
+        duplicateAssignmentPatternVariableError:
+            duplicateAssignmentPatternVariableError,
+        patternTypeMismatchInIrrefutableContextError:
+            patternTypeMismatchInIrrefutableContextError);
   }
 
   /// Computes the type schema for a variable pattern appearing in an assignment
@@ -342,7 +400,7 @@
         knownType: requiredType,
         matchFailsIfWrongType: false);
     if (matchedTypeIsSubtypeOfRequired) {
-      errors?.matchedTypeIsSubtypeOfRequired(
+      errors.matchedTypeIsSubtypeOfRequired(
         pattern: pattern,
         matchedType: matchedValueType,
         requiredType: requiredType,
@@ -371,27 +429,29 @@
   ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
-  /// Returns the static type of [expression].
+  /// Returns a [ConstantPatternResult] with the static type of [expression]
+  /// and information about reported errors.
   ///
   /// Stack effect: pushes (Expression).
-  Type analyzeConstantPattern(
+  ConstantPatternResult<Type, Error> analyzeConstantPattern(
       MatchContext<Node, Expression, Pattern, Type, Variable> context,
       Node node,
       Expression expression) {
     // Stack: ()
-    TypeAnalyzerErrors<Node, Node, Expression, Variable, Type, Pattern>?
-        errors = this.errors;
     Node? irrefutableContext = context.irrefutableContext;
+    Error? refutablePatternInIrrefutableContextError;
     if (irrefutableContext != null) {
-      errors?.refutablePatternInIrrefutableContext(
-          pattern: node, context: irrefutableContext);
+      refutablePatternInIrrefutableContextError =
+          errors.refutablePatternInIrrefutableContext(
+              pattern: node, context: irrefutableContext);
     }
     Type matchedType = flow.getMatchedValueType();
     Type expressionType = analyzeExpression(expression, matchedType);
     flow.constantPattern_end(expression, expressionType,
         patternsEnabled: options.patternsEnabled);
     // Stack: (Expression)
-    if (errors != null && !options.patternsEnabled) {
+    Error? caseExpressionTypeMismatchError;
+    if (!options.patternsEnabled) {
       Expression? switchScrutinee = context.switchScrutinee;
       if (switchScrutinee != null) {
         bool nullSafetyEnabled = options.nullSafetyEnabled;
@@ -399,7 +459,7 @@
             ? operations.isSubtypeOf(expressionType, matchedType)
             : operations.isAssignableTo(expressionType, matchedType);
         if (!matches) {
-          errors.caseExpressionTypeMismatch(
+          caseExpressionTypeMismatchError = errors.caseExpressionTypeMismatch(
               caseExpression: expression,
               scrutinee: switchScrutinee,
               caseExpressionType: expressionType,
@@ -408,7 +468,11 @@
         }
       }
     }
-    return expressionType;
+    return new ConstantPatternResult(
+        expressionType: expressionType,
+        refutablePatternInIrrefutableContextError:
+            refutablePatternInIrrefutableContextError,
+        caseExpressionTypeMismatchError: caseExpressionTypeMismatchError);
   }
 
   /// Computes the type schema for a constant pattern.
@@ -418,7 +482,7 @@
     // Constant patterns are only allowed in refutable contexts, and refutable
     // contexts don't propagate a type schema into the scrutinee.  So this
     // code path is only reachable if the user's code contains errors.
-    errors?.assertInErrorRecovery();
+    errors.assertInErrorRecovery();
     return unknownType;
   }
 
@@ -431,10 +495,11 @@
   ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
-  /// Returns the static type of the variable (possibly inferred).
+  /// Returns a [DeclaredVariablePatternResult] with the static type of the
+  /// variable (possibly inferred) and information about reported errors.
   ///
   /// Stack effect: none.
-  Type analyzeDeclaredVariablePattern(
+  DeclaredVariablePatternResult<Type, Error> analyzeDeclaredVariablePattern(
     MatchContext<Node, Expression, Pattern, Type, Variable> context,
     Pattern node,
     Variable variable,
@@ -445,29 +510,38 @@
     Type staticType =
         declaredType ?? variableTypeFromInitializerType(matchedType);
     Node? irrefutableContext = context.irrefutableContext;
+    Error? patternTypeMismatchInIrrefutableContextError;
     if (irrefutableContext != null &&
-        !operations.isAssignableTo(matchedType, staticType)) {
-      errors?.patternTypeMismatchInIrrefutableContext(
-          pattern: node,
-          context: irrefutableContext,
-          matchedType: matchedType,
-          requiredType: staticType);
+        !operations.isDynamic(matchedType) &&
+        !operations.isSubtypeOf(matchedType, staticType)) {
+      patternTypeMismatchInIrrefutableContextError =
+          errors.patternTypeMismatchInIrrefutableContext(
+              pattern: node,
+              context: irrefutableContext,
+              matchedType: matchedType,
+              requiredType: staticType);
     }
     flow.promoteForPattern(matchedType: matchedType, knownType: staticType);
+    // The promotion may have made the matched type even more specific than
+    // either `matchedType` or `staticType`, so fetch it again and use that
+    // in the call to `declaredVariablePattern` below.
+    matchedType = flow.getMatchedValueType();
     bool isImplicitlyTyped = declaredType == null;
     // TODO(paulberry): are we handling _isFinal correctly?
     int promotionKey = context.patternVariablePromotionKeys[variableName] =
         flow.declaredVariablePattern(
             matchedType: matchedType,
             staticType: staticType,
-            initializerExpression: context.initializer,
             isFinal: context.isFinal || isVariableFinal(variable),
             isLate: context.isLate,
             isImplicitlyTyped: isImplicitlyTyped);
     setVariableType(variable, staticType);
     (context.componentVariables[variableName] ??= []).add(variable);
     flow.assignMatchedPatternVariable(variable, promotionKey);
-    return staticType;
+    return new DeclaredVariablePatternResult(
+        staticType: staticType,
+        patternTypeMismatchInIrrefutableContextError:
+            patternTypeMismatchInIrrefutableContextError);
   }
 
   /// Computes the type schema for a variable pattern in a non-assignment
@@ -509,12 +583,15 @@
   /// wishes to use to represent that variable.  This is used to join together
   /// variables that appear in different branches of logical-or patterns.
   ///
+  /// Returns a [IfCaseStatementResult] with the static type of [expression] and
+  /// information about reported errors.
+  ///
   /// Stack effect: pushes (Expression scrutinee, Pattern, Expression guard,
   /// CollectionElement ifTrue, CollectionElement ifFalse).  If there is no
   /// `else` clause, the representation for `ifFalse` will be pushed by
   /// [handleNoCollectionElement].  If there is no guard, the representation
   /// for `guard` will be pushed by [handleNoGuard].
-  void analyzeIfCaseElement({
+  IfCaseStatementResult<Type, Error> analyzeIfCaseElement({
     required Node node,
     required Expression expression,
     required Pattern pattern,
@@ -544,14 +621,21 @@
     _finishJoinedPatternVariables(
         variables, componentVariables, patternVariablePromotionKeys,
         location: JoinedPatternVariableLocation.singlePattern);
+    Error? nonBooleanGuardError;
+    Type? guardType;
     if (guard != null) {
-      _checkGuardType(guard, analyzeExpression(guard, boolType));
+      guardType = analyzeExpression(guard, boolType);
+      nonBooleanGuardError = _checkGuardType(guard, guardType);
     } else {
       handleNoGuard(node, 0);
     }
     // Stack: (Expression, Pattern, Guard)
     flow.ifCaseStatement_thenBegin(guard);
     _analyzeIfElementCommon(node, ifTrue, ifFalse, context);
+    return new IfCaseStatementResult(
+        matchedExpressionType: initializerType,
+        nonBooleanGuardError: nonBooleanGuardError,
+        guardType: guardType);
   }
 
   /// Analyzes a statement of the form `if (expression case pattern) ifTrue` or
@@ -561,14 +645,15 @@
   /// the expression, [pattern] for the pattern to match, [ifTrue] for the
   /// "then" branch, and [ifFalse] for the "else" branch (if present).
   ///
-  /// Returns the static type of [expression].
+  /// Returns a [IfCaseStatementResult] with the static type of [expression] and
+  /// information about reported errors.
   ///
   /// Stack effect: pushes (Expression scrutinee, Pattern, Expression guard,
   /// Statement ifTrue, Statement ifFalse).  If there is no `else` clause, the
   /// representation for `ifFalse` will be pushed by [handleNoStatement].  If
   /// there is no guard, the representation for `guard` will be pushed by
   /// [handleNoGuard].
-  Type analyzeIfCaseStatement(
+  IfCaseStatementResult<Type, Error> analyzeIfCaseStatement(
     Statement node,
     Expression expression,
     Pattern pattern,
@@ -603,15 +688,21 @@
 
     handle_ifCaseStatement_afterPattern(node: node);
     // Stack: (Expression, Pattern)
+    Error? nonBooleanGuardError;
+    Type? guardType;
     if (guard != null) {
-      _checkGuardType(guard, analyzeExpression(guard, boolType));
+      guardType = analyzeExpression(guard, boolType);
+      nonBooleanGuardError = _checkGuardType(guard, guardType);
     } else {
       handleNoGuard(node, 0);
     }
     // Stack: (Expression, Pattern, Guard)
     flow.ifCaseStatement_thenBegin(guard);
     _analyzeIfCommon(node, ifTrue, ifFalse);
-    return initializerType;
+    return new IfCaseStatementResult(
+        matchedExpressionType: initializerType,
+        nonBooleanGuardError: nonBooleanGuardError,
+        guardType: guardType);
   }
 
   /// Analyzes a collection element of the form `if (condition) ifTrue` or
@@ -677,10 +768,13 @@
   /// the list element type (if explicitly supplied), and [elements] is the
   /// list of subpatterns.
   ///
+  /// Returns a [ListPatternResult] with the required type and information about
+  /// reported errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: pushes (n * Pattern) where n = elements.length.
-  Type analyzeListPattern(
+  ListPatternResult<Type, Error> analyzeListPattern(
       MatchContext<Node, Expression, Pattern, Type, Variable> context,
       Pattern node,
       {Type? elementType,
@@ -706,10 +800,12 @@
         matchMayFailEvenIfCorrectType: true);
     // Stack: ()
     Node? previousRestPattern;
-    for (Node element in elements) {
+    Map<int, Error>? duplicateRestPatternErrors;
+    for (int i = 0; i < elements.length; i++) {
+      Node element = elements[i];
       if (isRestPatternElement(element)) {
         if (previousRestPattern != null) {
-          errors?.duplicateRestPattern(
+          (duplicateRestPatternErrors ??= {})[i] = errors.duplicateRestPattern(
             mapOrListPattern: node,
             original: previousRestPattern,
             duplicate: element,
@@ -733,15 +829,21 @@
     }
     // Stack: (n * Pattern) where n = elements.length
     Node? irrefutableContext = context.irrefutableContext;
+    Error? patternTypeMismatchInIrrefutableContextError;
     if (irrefutableContext != null &&
         !operations.isAssignableTo(matchedType, requiredType)) {
-      errors?.patternTypeMismatchInIrrefutableContext(
-          pattern: node,
-          context: irrefutableContext,
-          matchedType: matchedType,
-          requiredType: requiredType);
+      patternTypeMismatchInIrrefutableContextError =
+          errors.patternTypeMismatchInIrrefutableContext(
+              pattern: node,
+              context: irrefutableContext,
+              matchedType: matchedType,
+              requiredType: requiredType);
     }
-    return requiredType;
+    return new ListPatternResult(
+        requiredType: requiredType,
+        duplicateRestPatternErrors: duplicateRestPatternErrors,
+        patternTypeMismatchInIrrefutableContextError:
+            patternTypeMismatchInIrrefutableContextError);
   }
 
   /// Computes the type schema for a list pattern.  [elementType] is the list
@@ -825,18 +927,22 @@
   /// Analyzes a logical-or pattern.  [node] is the pattern itself, and [lhs]
   /// and [rhs] are the left and right sides of the `||` operator.
   ///
+  /// Returns a [LogicalOrPatternResult] with information about reported errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: pushes (Pattern left, Pattern right)
-  void analyzeLogicalOrPattern(
+  LogicalOrPatternResult<Error> analyzeLogicalOrPattern(
       MatchContext<Node, Expression, Pattern, Type, Variable> context,
       Pattern node,
       Node lhs,
       Node rhs) {
     Node? irrefutableContext = context.irrefutableContext;
+    Error? refutablePatternInIrrefutableContextError;
     if (irrefutableContext != null) {
-      errors?.refutablePatternInIrrefutableContext(
-          pattern: node, context: irrefutableContext);
+      refutablePatternInIrrefutableContextError =
+          errors.refutablePatternInIrrefutableContext(
+              pattern: node, context: irrefutableContext);
       // Avoid cascading errors
       context = context.makeRefutable();
     }
@@ -892,6 +998,9 @@
     // flow control branches, the normal join process will combine promotions
     // accordingly.
     flow.logicalOrPattern_end();
+    return new LogicalOrPatternResult(
+        refutablePatternInIrrefutableContextError:
+            refutablePatternInIrrefutableContextError);
   }
 
   /// Computes the type schema for a logical-or pattern.  [lhs] and [rhs] are
@@ -902,7 +1011,7 @@
     // Logical-or patterns are only allowed in refutable contexts, and
     // refutable contexts don't propagate a type schema into the scrutinee.
     // So this code path is only reachable if the user's code contains errors.
-    errors?.assertInErrorRecovery();
+    errors.assertInErrorRecovery();
     return unknownType;
   }
 
@@ -910,10 +1019,13 @@
   /// contain explicit type arguments (if specified), and [elements] is the
   /// list of subpatterns.
   ///
+  /// Returns a [MapPatternResult] with the required type and information about
+  /// reported errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: pushes (n * MapPatternElement) where n = elements.length.
-  Type analyzeMapPattern(
+  MapPatternResult<Type, Error> analyzeMapPattern(
     MatchContext<Node, Expression, Pattern, Type, Variable> context,
     Pattern node, {
     required MapPatternTypeArguments<Type>? typeArguments,
@@ -955,10 +1067,12 @@
 
     bool hasDuplicateRestPatternReported = false;
     Node? previousRestPattern;
-    for (Node element in elements) {
+    Map<int, Error>? duplicateRestPatternErrors;
+    for (int i = 0; i < elements.length; i++) {
+      Node element = elements[i];
       if (isRestPatternElement(element)) {
         if (previousRestPattern != null) {
-          errors?.duplicateRestPattern(
+          (duplicateRestPatternErrors ??= {})[i] = errors.duplicateRestPattern(
             mapOrListPattern: node,
             original: previousRestPattern,
             duplicate: element,
@@ -973,24 +1087,24 @@
       Node element = elements[i];
       MapPatternEntry<Expression, Pattern>? entry = getMapPatternEntry(element);
       if (entry != null) {
-        analyzeExpression(entry.key, keyContext);
+        Type keyType = analyzeExpression(entry.key, keyContext);
         flow.pushSubpattern(valueType);
         dispatchPattern(
           context.withUnnecessaryWildcardKind(null),
           entry.value,
         );
-        handleMapPatternEntry(node, element);
+        handleMapPatternEntry(node, element, keyType);
         flow.popSubpattern();
       } else {
         assert(isRestPatternElement(element));
         if (!hasDuplicateRestPatternReported) {
           if (i != elements.length - 1) {
-            errors?.restPatternNotLastInMap(node: node, element: element);
+            errors.restPatternNotLastInMap(node: node, element: element);
           }
         }
         Pattern? subPattern = getRestPatternElementPattern(element);
         if (subPattern != null) {
-          errors?.restPatternWithSubPatternInMap(node: node, element: element);
+          errors.restPatternWithSubPatternInMap(node: node, element: element);
           flow.pushSubpattern(dynamicType);
           dispatchPattern(
             context.withUnnecessaryWildcardKind(null),
@@ -1003,16 +1117,22 @@
     }
     // Stack: (n * MapPatternElement) where n = elements.length
     Node? irrefutableContext = context.irrefutableContext;
+    Error? patternTypeMismatchInIrrefutableContextError;
     if (irrefutableContext != null &&
         !operations.isAssignableTo(matchedType, requiredType)) {
-      errors?.patternTypeMismatchInIrrefutableContext(
+      patternTypeMismatchInIrrefutableContextError =
+          errors.patternTypeMismatchInIrrefutableContext(
         pattern: node,
         context: irrefutableContext,
         matchedType: matchedType,
         requiredType: requiredType,
       );
     }
-    return requiredType;
+    return new MapPatternResult(
+        requiredType: requiredType,
+        duplicateRestPatternErrors: duplicateRestPatternErrors,
+        patternTypeMismatchInIrrefutableContextError:
+            patternTypeMismatchInIrrefutableContextError);
   }
 
   /// Computes the type schema for a map pattern.  [typeArguments] contain
@@ -1053,25 +1173,32 @@
   /// itself, [innerPattern] is the sub-pattern, and [isAssert] indicates
   /// whether this is a null-check or a null-assert pattern.
   ///
+  /// Returns a [NullCheckOrAssertPatternResult] with information about
+  /// reported errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: pushes (Pattern innerPattern).
-  void analyzeNullCheckOrAssertPattern(
+  NullCheckOrAssertPatternResult<Error> analyzeNullCheckOrAssertPattern(
       MatchContext<Node, Expression, Pattern, Type, Variable> context,
       Pattern node,
       Pattern innerPattern,
       {required bool isAssert}) {
     // Stack: ()
+    Error? refutablePatternInIrrefutableContextError;
+    Error? matchedTypeIsStrictlyNonNullableError;
     Node? irrefutableContext = context.irrefutableContext;
     bool matchedTypeIsStrictlyNonNullable =
         flow.nullCheckOrAssertPattern_begin(isAssert: isAssert);
     if (irrefutableContext != null && !isAssert) {
-      errors?.refutablePatternInIrrefutableContext(
-          pattern: node, context: irrefutableContext);
+      refutablePatternInIrrefutableContextError =
+          errors.refutablePatternInIrrefutableContext(
+              pattern: node, context: irrefutableContext);
       // Avoid cascading errors
       context = context.makeRefutable();
     } else if (matchedTypeIsStrictlyNonNullable) {
-      errors?.matchedTypeIsStrictlyNonNullable(
+      matchedTypeIsStrictlyNonNullableError =
+          errors.matchedTypeIsStrictlyNonNullable(
         pattern: node,
         matchedType: flow.getMatchedValueType(),
       );
@@ -1082,6 +1209,12 @@
     );
     // Stack: (Pattern)
     flow.nullCheckOrAssertPattern_end();
+
+    return new NullCheckOrAssertPatternResult(
+        refutablePatternInIrrefutableContextError:
+            refutablePatternInIrrefutableContextError,
+        matchedTypeIsStrictlyNonNullableError:
+            matchedTypeIsStrictlyNonNullableError);
   }
 
   /// Computes the type schema for a null-check or null-assert pattern.
@@ -1097,7 +1230,7 @@
       // Null-check patterns are only allowed in refutable contexts, and
       // refutable contexts don't propagate a type schema into the scrutinee.
       // So this code path is only reachable if the user's code contains errors.
-      errors?.assertInErrorRecovery();
+      errors.assertInErrorRecovery();
       return unknownType;
     }
   }
@@ -1107,15 +1240,19 @@
   /// irrefutable contexts, but can be `null` in refutable contexts, then
   /// [downwardInferObjectPatternRequiredType] is invoked to infer the type.
   ///
+  /// Returns a [ObjectPatternResult] with the required type and information
+  /// about reported errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: pushes (n * Pattern) where n = fields.length.
-  Type analyzeObjectPattern(
+  ObjectPatternResult<Type, Error> analyzeObjectPattern(
     MatchContext<Node, Expression, Pattern, Type, Variable> context,
     Pattern node, {
     required List<RecordPatternField<Node, Pattern>> fields,
   }) {
-    _reportDuplicateRecordPatternFields(node, fields);
+    Map<int, Error>? duplicateRecordPatternFieldErrors =
+        _reportDuplicateRecordPatternFields(node, fields);
 
     Type matchedType = flow.getMatchedValueType();
     Type requiredType = downwardInferObjectPatternRequiredType(
@@ -1133,9 +1270,11 @@
     }
 
     Node? irrefutableContext = context.irrefutableContext;
+    Error? patternTypeMismatchInIrrefutableContextError;
     if (irrefutableContext != null &&
         !operations.isAssignableTo(matchedType, requiredType)) {
-      errors?.patternTypeMismatchInIrrefutableContext(
+      patternTypeMismatchInIrrefutableContextError =
+          errors.patternTypeMismatchInIrrefutableContext(
         pattern: node,
         context: irrefutableContext,
         matchedType: matchedType,
@@ -1159,7 +1298,11 @@
     }
     // Stack: (n * Pattern) where n = fields.length
 
-    return requiredType;
+    return new ObjectPatternResult(
+        requiredType: requiredType,
+        duplicateRecordPatternFieldErrors: duplicateRecordPatternFieldErrors,
+        patternTypeMismatchInIrrefutableContextError:
+            patternTypeMismatchInIrrefutableContextError);
   }
 
   /// Computes the type schema for an object pattern.  [type] is the type
@@ -1188,7 +1331,6 @@
     dispatchPattern(
       new MatchContext<Node, Expression, Pattern, Type, Variable>(
         isFinal: false,
-        initializer: rhs,
         irrefutableContext: node,
         assignedVariables: <Variable, Pattern>{},
         componentVariables: componentVariables,
@@ -1199,7 +1341,7 @@
     if (componentVariables.isNotEmpty) {
       // Declared pattern variables should never appear in a pattern assignment
       // so this should never happen.
-      errors?.assertInErrorRecovery();
+      errors.assertInErrorRecovery();
     }
     flow.patternAssignment_end();
     // Stack: (Expression, Pattern)
@@ -1218,7 +1360,9 @@
   /// `for (<keyword> <pattern> in <expression>) <body>`
   ///
   /// Stack effect: pushes (Expression, Pattern).
-  void analyzePatternForIn({
+  ///
+  /// Returns a [PatternForInResult] containing information on reported errors.
+  PatternForInResult<Error> analyzePatternForIn({
     required Node node,
     required bool hasAwait,
     required Pattern pattern,
@@ -1233,6 +1377,7 @@
     Type expressionType = analyzeExpression(expression, expressionTypeSchema);
     // Stack: (Expression)
 
+    Error? patternForInExpressionIsNotIterableError;
     Type? elementType = hasAwait
         ? operations.matchStreamType(expressionType)
         : operations.matchIterableType(expressionType);
@@ -1240,7 +1385,8 @@
       if (operations.isDynamic(expressionType)) {
         elementType = dynamicType;
       } else {
-        errors?.patternForInExpressionIsNotIterable(
+        patternForInExpressionIsNotIterableError =
+            errors.patternForInExpressionIsNotIterable(
           node: node,
           expression: expression,
           expressionType: expressionType,
@@ -1267,6 +1413,10 @@
     dispatchBody();
     flow.forEach_end();
     flow.patternForIn_end();
+
+    return new PatternForInResult(
+        patternForInExpressionIsNotIterableError:
+            patternForInExpressionIsNotIterableError);
   }
 
   /// Analyzes a patternVariableDeclaration node of the form
@@ -1289,7 +1439,7 @@
       {required bool isFinal, required bool isLate}) {
     // Stack: ()
     if (isLate && !isVariablePattern(pattern)) {
-      errors?.patternDoesNotAllowLate(pattern: pattern);
+      errors.patternDoesNotAllowLate(pattern: pattern);
     }
     if (isLate) {
       flow.lateInitializer_begin(node);
@@ -1308,7 +1458,6 @@
       new MatchContext<Node, Expression, Pattern, Type, Variable>(
         isFinal: isFinal,
         isLate: isLate,
-        initializer: initializer,
         irrefutableContext: node,
         componentVariables: componentVariables,
         patternVariablePromotionKeys: patternVariablePromotionKeys,
@@ -1326,10 +1475,13 @@
   /// Analyzes a record pattern.  [node] is the pattern itself, and [fields]
   /// is the list of subpatterns.
   ///
+  /// Returns a [RecordPatternResult] with the required type and information
+  /// about reported errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: pushes (n * Pattern) where n = fields.length.
-  Type analyzeRecordPattern(
+  RecordPatternResult<Type, Error> analyzeRecordPattern(
     MatchContext<Node, Expression, Pattern, Type, Variable> context,
     Pattern node, {
     required List<RecordPatternField<Node, Pattern>> fields,
@@ -1361,7 +1513,8 @@
       }
     }
 
-    _reportDuplicateRecordPatternFields(node, fields);
+    Map<int, Error>? duplicateRecordPatternFieldErrors =
+        _reportDuplicateRecordPatternFields(node, fields);
 
     // Build the required type.
     int requiredTypePositionalCount = 0;
@@ -1406,9 +1559,11 @@
     // Stack: (n * Pattern) where n = fields.length
 
     Node? irrefutableContext = context.irrefutableContext;
+    Error? patternTypeMismatchInIrrefutableContextError;
     if (irrefutableContext != null &&
         !operations.isAssignableTo(matchedType, requiredType)) {
-      errors?.patternTypeMismatchInIrrefutableContext(
+      patternTypeMismatchInIrrefutableContextError =
+          errors.patternTypeMismatchInIrrefutableContext(
         pattern: node,
         context: irrefutableContext,
         matchedType: matchedType,
@@ -1422,7 +1577,11 @@
         matchedType: matchedType,
         knownType: demonstratedType,
         matchFailsIfWrongType: false);
-    return requiredType;
+    return new RecordPatternResult(
+        requiredType: requiredType,
+        duplicateRecordPatternFieldErrors: duplicateRecordPatternFieldErrors,
+        patternTypeMismatchInIrrefutableContextError:
+            patternTypeMismatchInIrrefutableContextError);
   }
 
   /// Computes the type schema for a record pattern.
@@ -1452,22 +1611,23 @@
   /// This method will invoke [resolveRelationalPatternOperator] to obtain
   /// information about the operator.
   ///
-  /// Returns the type of the [operand].
+  /// Returns a [RelationalPatternResult] with the type of the [operand] and
+  /// information about reported errors.
   ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: pushes (Expression).
-  Type analyzeRelationalPattern(
+  RelationalPatternResult<Type, Error> analyzeRelationalPattern(
       MatchContext<Node, Expression, Pattern, Type, Variable> context,
       Pattern node,
       Expression operand) {
     // Stack: ()
-    TypeAnalyzerErrors<Node, Node, Expression, Variable, Type, Pattern>?
-        errors = this.errors;
+    Error? refutablePatternInIrrefutableContextError;
     Node? irrefutableContext = context.irrefutableContext;
     if (irrefutableContext != null) {
-      errors?.refutablePatternInIrrefutableContext(
-          pattern: node, context: irrefutableContext);
+      refutablePatternInIrrefutableContextError =
+          errors.refutablePatternInIrrefutableContext(
+              pattern: node, context: irrefutableContext);
     }
     Type matchedValueType = flow.getMatchedValueType();
     RelationalOperatorResolution<Type>? operator =
@@ -1492,26 +1652,34 @@
         break;
     }
     // Stack: (Expression)
-    if (errors != null && operator != null) {
+    Error? argumentTypeNotAssignableError;
+    Error? operatorReturnTypeNotAssignableToBoolError;
+    if (operator != null) {
       Type argumentType =
           isEquality ? operations.promoteToNonNull(operandType) : operandType;
       if (!operations.isAssignableTo(argumentType, operator.parameterType)) {
-        errors.argumentTypeNotAssignable(
-          argument: operand,
-          argumentType: argumentType,
+        argumentTypeNotAssignableError =
+            errors.relationalPatternOperandTypeNotAssignable(
+          pattern: node,
+          operandType: argumentType,
           parameterType: operator.parameterType,
         );
       }
       if (!operations.isAssignableTo(operator.returnType, boolType)) {
-        errors.relationalPatternOperatorReturnTypeNotAssignableToBool(
+        operatorReturnTypeNotAssignableToBoolError =
+            errors.relationalPatternOperatorReturnTypeNotAssignableToBool(
           pattern: node,
           returnType: operator.returnType,
         );
       }
     }
-    // TODO(johnniwinther): This doesn't scale. We probably need to pass more
-    // information, for instance whether this was an erroneous case.
-    return operandType;
+    return new RelationalPatternResult(
+        operandType: operandType,
+        refutablePatternInIrrefutableContextError:
+            refutablePatternInIrrefutableContextError,
+        operatorReturnTypeNotAssignableToBoolError:
+            operatorReturnTypeNotAssignableToBoolError,
+        argumentTypeNotAssignableError: argumentTypeNotAssignableError);
   }
 
   /// Computes the type schema for a relational pattern.
@@ -1521,15 +1689,18 @@
     // Relational patterns are only allowed in refutable contexts, and refutable
     // contexts don't propagate a type schema into the scrutinee.  So this
     // code path is only reachable if the user's code contains errors.
-    errors?.assertInErrorRecovery();
+    errors.assertInErrorRecovery();
     return unknownType;
   }
 
   /// Analyzes an expression of the form `switch (expression) { cases }`.
   ///
+  /// Returns a [SwitchExpressionResult] with the static type of the switch
+  /// expression and information about reported errors.
+  ///
   /// Stack effect: pushes (Expression, n * ExpressionCase), where n is the
   /// number of cases.
-  SimpleTypeAnalysisResult<Type> analyzeSwitchExpression(
+  SwitchExpressionResult<Type, Error> analyzeSwitchExpression(
       Expression node, Expression scrutinee, int numCases, Type context) {
     // Stack: ()
     Type expressionType = analyzeExpression(scrutinee, unknownType);
@@ -1537,6 +1708,8 @@
     handleSwitchScrutinee(expressionType);
     flow.switchStatement_expressionEnd(null, scrutinee, expressionType);
     Type? lubType;
+    Map<int, Error>? nonBooleanGuardErrors;
+    Map<int, Type>? guardTypes;
     for (int i = 0; i < numCases; i++) {
       // Stack: (Expression, i * ExpressionCase)
       SwitchExpressionMemberInfo<Node, Expression, Variable> memberInfo =
@@ -1568,13 +1741,18 @@
         guard = memberInfo.head.guard;
         bool hasGuard = guard != null;
         if (hasGuard) {
-          _checkGuardType(guard, analyzeExpression(guard, boolType));
+          Type guardType = analyzeExpression(guard, boolType);
+          Error? nonBooleanGuardError = _checkGuardType(guard, guardType);
+          (guardTypes ??= {})[i] = guardType;
+          if (nonBooleanGuardError != null) {
+            (nonBooleanGuardErrors ??= {})[i] = nonBooleanGuardError;
+          }
           // Stack: (Expression, i * ExpressionCase, Pattern, Expression)
         } else {
           handleNoGuard(node, i);
           // Stack: (Expression, i * ExpressionCase, Pattern, Expression)
         }
-        handleCaseHead(node, caseIndex: i, subIndex: 0);
+        handleCaseHead(node, memberInfo.head, caseIndex: i, subIndex: 0);
       } else {
         handleDefault(node, caseIndex: i, subIndex: 0);
       }
@@ -1594,18 +1772,18 @@
     }
     lubType ??= dynamicType;
     // Stack: (Expression, numCases * ExpressionCase)
-    bool isProvenExhaustive = flow.switchStatement_end(true);
-    if (options.errorOnSwitchExhaustiveness && !isProvenExhaustive) {
-      errors?.nonExhaustiveSwitch(node: node, scrutineeType: expressionType);
-    }
-    return new SimpleTypeAnalysisResult<Type>(type: lubType);
+    flow.switchStatement_end(true);
+    return new SwitchExpressionResult(
+        type: lubType,
+        nonBooleanGuardErrors: nonBooleanGuardErrors,
+        guardTypes: guardTypes);
   }
 
   /// Analyzes a statement of the form `switch (expression) { cases }`.
   ///
   /// Stack effect: pushes (Expression, n * StatementCase), where n is the
   /// number of cases after merging together cases that share a body.
-  SwitchStatementTypeAnalysisResult<Type> analyzeSwitchStatement(
+  SwitchStatementTypeAnalysisResult<Type, Error> analyzeSwitchStatement(
       Statement node, Expression scrutinee, final int numCases) {
     // Stack: ()
     Type scrutineeType = analyzeExpression(scrutinee, unknownType);
@@ -1614,6 +1792,9 @@
     flow.switchStatement_expressionEnd(node, scrutinee, scrutineeType);
     bool hasDefault = false;
     bool lastCaseTerminates = true;
+    Map<int, Error>? switchCaseCompletesNormallyErrors;
+    Map<int, Map<int, Error>>? nonBooleanGuardErrors;
+    Map<int, Map<int, Type>>? guardTypes;
     for (int caseIndex = 0; caseIndex < numCases; caseIndex++) {
       // Stack: (Expression, numExecutionPaths * StatementCase)
       flow.switchStatement_beginAlternatives();
@@ -1653,13 +1834,21 @@
           //         numHeads * CaseHead, Pattern),
           guard = head.guard;
           if (guard != null) {
-            _checkGuardType(guard, analyzeExpression(guard, boolType));
+            Type guardType = analyzeExpression(guard, boolType);
+            Error? nonBooleanGuardError = _checkGuardType(guard, guardType);
+            ((guardTypes ??= {})[caseIndex] ??= {})[headIndex] = guardType;
+            if (nonBooleanGuardError != null) {
+              ((nonBooleanGuardErrors ??= {})[caseIndex] ??= {})[headIndex] =
+                  nonBooleanGuardError;
+            }
             // Stack: (Expression, numExecutionPaths * StatementCase,
             //         numHeads * CaseHead, Pattern, Expression),
           } else {
             handleNoGuard(node, caseIndex);
           }
-          handleCaseHead(node, caseIndex: caseIndex, subIndex: headIndex);
+          head = handleCaseHead(node, head,
+              caseIndex: caseIndex, subIndex: headIndex);
+          guard = head.guard;
         } else {
           hasDefault = true;
           handleDefault(node, caseIndex: caseIndex, subIndex: headIndex);
@@ -1695,7 +1884,8 @@
           options.nullSafetyEnabled &&
           !options.patternsEnabled &&
           !lastCaseTerminates) {
-        errors?.switchCaseCompletesNormally(node: node, caseIndex: caseIndex);
+        (switchCaseCompletesNormallyErrors ??= {})[caseIndex] = errors
+            .switchCaseCompletesNormally(node: node, caseIndex: caseIndex);
       }
       handleMergedStatementCase(node,
           caseIndex: caseIndex, isTerminating: lastCaseTerminates);
@@ -1714,18 +1904,16 @@
       isExhaustive = isLegacySwitchExhaustive(node, scrutineeType);
       requiresExhaustivenessValidation = false;
     }
-    bool isProvenExhaustive = flow.switchStatement_end(isExhaustive);
-    if (options.errorOnSwitchExhaustiveness &&
-        requiresExhaustivenessValidation &&
-        !isProvenExhaustive) {
-      errors?.nonExhaustiveSwitch(node: node, scrutineeType: scrutineeType);
-    }
-    return new SwitchStatementTypeAnalysisResult<Type>(
+    flow.switchStatement_end(isExhaustive);
+    return new SwitchStatementTypeAnalysisResult(
       hasDefault: hasDefault,
       isExhaustive: isExhaustive,
       lastCaseTerminates: lastCaseTerminates,
       requiresExhaustivenessValidation: requiresExhaustivenessValidation,
       scrutineeType: scrutineeType,
+      switchCaseCompletesNormallyErrors: switchCaseCompletesNormallyErrors,
+      nonBooleanGuardErrors: nonBooleanGuardErrors,
+      guardTypes: guardTypes,
     );
   }
 
@@ -1751,19 +1939,23 @@
 
   /// Analyzes a wildcard pattern.  [node] is the pattern.
   ///
+  /// Returns a [WildcardPattern] with information about reported errors.
+  ///
   /// See [dispatchPattern] for the meaning of [context].
   ///
   /// Stack effect: none.
-  void analyzeWildcardPattern({
+  WildcardPatternResult<Error> analyzeWildcardPattern({
     required MatchContext<Node, Expression, Pattern, Type, Variable> context,
     required Pattern node,
     required Type? declaredType,
   }) {
     Type matchedType = flow.getMatchedValueType();
     Node? irrefutableContext = context.irrefutableContext;
+    Error? patternTypeMismatchInIrrefutableContextError;
     if (irrefutableContext != null && declaredType != null) {
       if (!operations.isAssignableTo(matchedType, declaredType)) {
-        errors?.patternTypeMismatchInIrrefutableContext(
+        patternTypeMismatchInIrrefutableContextError =
+            errors.patternTypeMismatchInIrrefutableContext(
           pattern: node,
           context: irrefutableContext,
           matchedType: matchedType,
@@ -1783,11 +1975,14 @@
     UnnecessaryWildcardKind? unnecessaryWildcardKind =
         context.unnecessaryWildcardKind;
     if (isAlwaysMatching && unnecessaryWildcardKind != null) {
-      errors?.unnecessaryWildcardPattern(
+      errors.unnecessaryWildcardPattern(
         pattern: node,
         kind: unnecessaryWildcardKind,
       );
     }
+    return new WildcardPatternResult(
+        patternTypeMismatchInIrrefutableContextError:
+            patternTypeMismatchInIrrefutableContextError);
   }
 
   /// Computes the type schema for a wildcard pattern.  [declaredType] is the
@@ -1866,7 +2061,7 @@
   void finishJoinedPatternVariable(
     Variable variable, {
     required JoinedPatternVariableLocation location,
-    required bool isConsistent,
+    required JoinedPatternVariableInconsistency inconsistency,
     required bool isFinal,
     required Type type,
   });
@@ -1938,11 +2133,15 @@
   /// Called after visiting a single `case` clause, consisting of a pattern and
   /// an optional guard.
   ///
-  /// [node] is the enclosing switch statement or switch expression and
+  /// [node] is the enclosing switch statement or switch expression,
+  /// [head] is the head to be handled, and
   /// [caseIndex] is the index of the `case` clause.
   ///
+  /// Returns the updated case head.
+  ///
   /// Stack effect: pops (Pattern, Expression) and pushes (CaseHead).
-  void handleCaseHead(Node node,
+  CaseHeadOrDefaultInfo<Node, Expression, Variable> handleCaseHead(
+      Node node, CaseHeadOrDefaultInfo<Node, Expression, Variable> head,
       {required int caseIndex, required int subIndex});
 
   /// Called after visiting a `default` clause.
@@ -1966,7 +2165,8 @@
   /// Called after visiting an entry element in a map pattern.
   ///
   /// Stack effect: pushes (MapPatternElement).
-  void handleMapPatternEntry(Pattern container, Node entryElement);
+  void handleMapPatternEntry(
+      Pattern container, Node entryElement, Type keyType);
 
   /// Called after visiting a rest element in a map pattern.
   ///
@@ -2142,14 +2342,15 @@
     // Stack: (CollectionElement ifTrue, CollectionElement ifFalse)
   }
 
-  void _checkGuardType(Expression expression, Type type) {
+  Error? _checkGuardType(Expression expression, Type type) {
     // TODO(paulberry): harmonize this with analyzer's checkForNonBoolExpression
     // TODO(paulberry): spec says the type must be `bool` or `dynamic`.  This
     // logic permits `T extends bool`, `T promoted to bool`, or `Never`.  What
     // do we want?
     if (!operations.isAssignableTo(type, boolType)) {
-      errors?.nonBooleanCondition(node: expression);
+      return errors.nonBooleanCondition(node: expression);
     }
+    return null;
   }
 
   void _finishJoinedPatternVariables(
@@ -2200,7 +2401,7 @@
           if (inconsistencyFound &&
               location == JoinedPatternVariableLocation.singlePattern &&
               variable != null) {
-            errors?.inconsistentJoinedPatternVariable(
+            errors.inconsistentJoinedPatternVariable(
                 variable: variable, component: component);
           }
         }
@@ -2209,8 +2410,10 @@
         if (!isIdenticalToComponent) {
           finishJoinedPatternVariable(variable,
               location: location,
-              isConsistent:
-                  typeIfConsistent != null && isFinalIfConsistent != null,
+              inconsistency: typeIfConsistent != null &&
+                      isFinalIfConsistent != null
+                  ? JoinedPatternVariableInconsistency.none
+                  : JoinedPatternVariableInconsistency.differentFinalityOrType,
               isFinal: isFinalIfConsistent ?? false,
               type: typeIfConsistent ?? errorType);
           flow.assignMatchedPatternVariable(variable, promotionKey);
@@ -2263,17 +2466,17 @@
   }
 
   /// Reports errors for duplicate named record fields.
-  void _reportDuplicateRecordPatternFields(
-    Pattern pattern,
-    List<RecordPatternField<Node, Pattern>> fields,
-  ) {
+  Map<int, Error>? _reportDuplicateRecordPatternFields(
+      Pattern pattern, List<RecordPatternField<Node, Pattern>> fields) {
+    Map<int, Error>? errorResults;
     Map<String, RecordPatternField<Node, Pattern>> nameToField = {};
-    for (RecordPatternField<Node, Pattern> field in fields) {
+    for (int i = 0; i < fields.length; i++) {
+      RecordPatternField<Node, Pattern> field = fields[i];
       String? name = field.name;
       if (name != null) {
         RecordPatternField<Node, Pattern>? original = nameToField[name];
         if (original != null) {
-          errors?.duplicateRecordPatternField(
+          (errorResults ??= {})[i] = errors.duplicateRecordPatternField(
             objectOrRecordPattern: pattern,
             name: name,
             original: original,
@@ -2284,6 +2487,7 @@
         }
       }
     }
+    return errorResults;
   }
 
   bool _structurallyEqualAfterNormTypes(Type type1, Type type2) {
@@ -2301,18 +2505,11 @@
     Expression extends Node,
     Variable extends Object,
     Type extends Object,
-    Pattern extends Node> implements TypeAnalyzerErrorsBase {
-  /// Called if [argument] has type [argumentType], which is not assignable
-  /// to [parameterType].
-  void argumentTypeNotAssignable({
-    required Expression argument,
-    required Type argumentType,
-    required Type parameterType,
-  });
-
+    Pattern extends Node,
+    Error> implements TypeAnalyzerErrorsBase {
   /// Called if pattern support is disabled and a case constant's static type
   /// doesn't properly match the scrutinee's static type.
-  void caseExpressionTypeMismatch(
+  Error caseExpressionTypeMismatch(
       {required Expression scrutinee,
       required Expression caseExpression,
       required Type scrutineeType,
@@ -2320,14 +2517,16 @@
       required bool nullSafetyEnabled});
 
   /// Called for variable that is assigned more than once.
-  void duplicateAssignmentPatternVariable({
+  ///
+  /// Returns an error object that is passed on the the caller.
+  Error duplicateAssignmentPatternVariable({
     required Variable variable,
     required Pattern original,
     required Pattern duplicate,
   });
 
   /// Called for a pair of named fields have the same name.
-  void duplicateRecordPatternField({
+  Error duplicateRecordPatternField({
     required Pattern objectOrRecordPattern,
     required String name,
     required RecordPatternField<Node, Pattern> original,
@@ -2335,7 +2534,7 @@
   });
 
   /// Called for a duplicate rest pattern found in a list or map pattern.
-  void duplicateRestPattern({
+  Error duplicateRestPattern({
     required Pattern mapOrListPattern,
     required Node original,
     required Node duplicate,
@@ -2351,7 +2550,7 @@
 
   /// Called when a null-assert or null-check pattern is used with the matched
   /// type that is strictly non-nullable, so the null check is not necessary.
-  void matchedTypeIsStrictlyNonNullable({
+  Error matchedTypeIsStrictlyNonNullable({
     required Pattern pattern,
     required Type matchedType,
   });
@@ -2365,16 +2564,7 @@
   });
 
   /// Called if the static type of a condition is not assignable to `bool`.
-  void nonBooleanCondition({required Expression node});
-
-  /// Called if [TypeAnalyzerOptions.errorOnSwitchExhaustiveness] is `true`, and
-  /// a switch that is required to be exhaustive cannot be proven by flow
-  /// analysis to be exhaustive.
-  ///
-  /// [node] is the offending switch expression or switch statement, and
-  /// [scrutineeType] is the static type of the switch statement's scrutinee
-  /// expression.
-  void nonExhaustiveSwitch({required Node node, required Type scrutineeType});
+  Error nonBooleanCondition({required Expression node});
 
   /// Called if a pattern is illegally used in a variable declaration statement
   /// that is marked `late`, and that pattern is not allowed in such a
@@ -2388,7 +2578,7 @@
   /// that should be an `Iterable` (or dynamic) is actually not.
   ///
   /// [expressionType] is the actual type of the [expression].
-  void patternForInExpressionIsNotIterable({
+  Error patternForInExpressionIsNotIterable({
     required Node node,
     required Expression expression,
     required Type expressionType,
@@ -2401,7 +2591,7 @@
   /// the containing AST node that established an irrefutable context,
   /// [matchedType] is the matched type, and [requiredType] is the required
   /// type.
-  void patternTypeMismatchInIrrefutableContext(
+  Error patternTypeMismatchInIrrefutableContext(
       {required Pattern pattern,
       required Node context,
       required Type matchedType,
@@ -2413,12 +2603,20 @@
   /// containing AST node that established an irrefutable context.
   ///
   /// TODO(paulberry): move this error reporting to the parser.
-  void refutablePatternInIrrefutableContext(
+  Error refutablePatternInIrrefutableContext(
       {required Node pattern, required Node context});
 
+  /// Called if the operand of the [pattern] has the type [operandType], which
+  /// is not assignable to [parameterType] of the invoked relational operator.
+  Error relationalPatternOperandTypeNotAssignable({
+    required Pattern pattern,
+    required Type operandType,
+    required Type parameterType,
+  });
+
   /// Called if the [returnType] of the invoked relational operator is not
   /// assignable to `bool`.
-  void relationalPatternOperatorReturnTypeNotAssignableToBool({
+  Error relationalPatternOperatorReturnTypeNotAssignableToBool({
     required Pattern pattern,
     required Type returnType,
   });
@@ -2440,7 +2638,7 @@
   ///
   /// [node] is the AST node of the switch statement.  [caseIndex] is the index
   /// of the merged case with the erroneous case body.
-  void switchCaseCompletesNormally(
+  Error switchCaseCompletesNormally(
       {required Statement node, required int caseIndex});
 
   /// Called when a wildcard pattern appears in the context where it is not
@@ -2473,19 +2671,6 @@
 
   final bool patternsEnabled;
 
-  /// If `true`, the type analyzer should generate errors if it encounters a
-  /// switch that is required to be exhaustive, but cannot be proven to be
-  /// exhaustive by flow analysis.
-  ///
-  /// This option is intended as a temporary workaround if we want to ship an
-  /// early beta of the "patterns" feature before exhaustiveness checking is
-  /// sufficiently ready.
-  ///
-  /// TODO(paulberry): remove this option when it is no longer needed.
-  final bool errorOnSwitchExhaustiveness;
-
   TypeAnalyzerOptions(
-      {required this.nullSafetyEnabled,
-      required this.patternsEnabled,
-      this.errorOnSwitchExhaustiveness = false});
+      {required this.nullSafetyEnabled, required this.patternsEnabled});
 }
diff --git a/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart b/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart
index 1039b1b..3c122b5 100644
--- a/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart
+++ b/_fe_analyzer_shared/lib/src/type_inference/variable_bindings.dart
@@ -90,7 +90,7 @@
   Variable joinPatternVariables({
     required Object key,
     required List<Variable> components,
-    required bool isConsistent,
+    required JoinedPatternVariableInconsistency inconsistency,
   });
 
   /// Updates the binder after visiting a logical-or pattern, joins variables
@@ -109,7 +109,7 @@
           joinPatternVariables(
             key: node,
             components: [leftVariable, rightVariable],
-            isConsistent: true,
+            inconsistency: JoinedPatternVariableInconsistency.none,
           ),
         );
       } else {
@@ -124,7 +124,7 @@
           joinPatternVariables(
             key: node,
             components: [leftVariable],
-            isConsistent: false,
+            inconsistency: JoinedPatternVariableInconsistency.logicalOr,
           ),
         );
       }
@@ -143,7 +143,7 @@
         joinPatternVariables(
           key: node,
           components: [rightVariable],
-          isConsistent: false,
+          inconsistency: JoinedPatternVariableInconsistency.logicalOr,
         ),
       );
     }
@@ -165,6 +165,7 @@
   void switchStatementSharedCaseScopeEmpty(Object key) {
     _SharedCaseScope<Variable> sharedScope = _sharedCaseScopes.last;
     assert(sharedScope.key == key);
+    sharedScope.hasLabel = true;
     sharedScope.addAll({});
   }
 
@@ -183,13 +184,17 @@
         in sharedScope.variables.entries) {
       _SharedCaseScopeVariable<Variable> sharedVariable = entry.value;
       List<Variable> variables = sharedVariable.variables;
-      if (sharedVariable.isConsistent && variables.length == 1) {
+      if (sharedVariable.allCases && variables.length == 1) {
         result[entry.key] = variables[0];
       } else {
         result[entry.key] = joinPatternVariables(
           key: key,
           components: variables,
-          isConsistent: sharedVariable.isConsistent,
+          inconsistency: sharedVariable.allCases
+              ? JoinedPatternVariableInconsistency.none
+              : sharedScope.hasLabel
+                  ? JoinedPatternVariableInconsistency.sharedCaseHasLabel
+                  : JoinedPatternVariableInconsistency.sharedCaseAbsent,
         );
       }
     }
@@ -230,6 +235,7 @@
 class _SharedCaseScope<Variable extends Object> {
   final Object key;
   bool isEmpty = true;
+  bool hasLabel = false;
   Map<String, _SharedCaseScopeVariable<Variable>> variables = {};
 
   _SharedCaseScope(this.key);
@@ -253,7 +259,7 @@
         if (newVariable != null) {
           variable.variables.add(newVariable);
         } else {
-          variable.isConsistent = false;
+          variable.allCases = false;
         }
       }
       for (MapEntry<String, Variable> newEntry in newVariables.entries) {
@@ -261,7 +267,7 @@
         Variable newVariable = newEntry.value;
         if (!variables.containsKey(name)) {
           _getVariable(name)
-            ..isConsistent = false
+            ..allCases = false
             ..variables.add(newVariable);
         }
       }
@@ -274,6 +280,6 @@
 }
 
 class _SharedCaseScopeVariable<Variable extends Object> {
-  bool isConsistent = true;
+  bool allCases = true;
   final List<Variable> variables = [];
 }
diff --git a/_fe_analyzer_shared/pubspec.yaml b/_fe_analyzer_shared/pubspec.yaml
index cf159cb..66f18b0 100644
--- a/_fe_analyzer_shared/pubspec.yaml
+++ b/_fe_analyzer_shared/pubspec.yaml
@@ -1,5 +1,5 @@
 name: _fe_analyzer_shared
-version: 54.0.0
+version: 58.0.0
 description: Logic that is shared between the front_end and analyzer packages.
 repository: https://github.com/dart-lang/sdk/tree/main/pkg/_fe_analyzer_shared
 
diff --git a/analyzer/BUILD.gn b/analyzer/BUILD.gn
index 5da73bc..cb801f4 100644
--- a/analyzer/BUILD.gn
+++ b/analyzer/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for analyzer-5.6.0
+# This file is generated by package_importer.py for analyzer-5.10.0
 
 import("//build/dart/dart_library.gni")
 
 dart_library("analyzer") {
   package_name = "analyzer"
 
-  language_version = "2.17"
+  language_version = "2.19"
 
   disable_analysis = true
 
@@ -153,6 +153,7 @@
     "src/dart/element/replacement_visitor.dart",
     "src/dart/element/runtime_type_equality.dart",
     "src/dart/element/scope.dart",
+    "src/dart/element/since_sdk_version.dart",
     "src/dart/element/subtype.dart",
     "src/dart/element/top_merge.dart",
     "src/dart/element/type.dart",
@@ -226,6 +227,7 @@
     "src/error.dart",
     "src/error/analyzer_error_code.dart",
     "src/error/assignment_verifier.dart",
+    "src/error/base_or_final_type_verifier.dart",
     "src/error/best_practices_verifier.dart",
     "src/error/bool_expression_verifier.dart",
     "src/error/codes.dart",
@@ -421,6 +423,7 @@
     "src/utilities/extensions/stream.dart",
     "src/utilities/extensions/string.dart",
     "src/utilities/legacy.dart",
+    "src/utilities/since_sdk_cache.dart",
     "src/utilities/uri_cache.dart",
     "src/workspace/basic.dart",
     "src/workspace/blaze.dart",
diff --git a/analyzer/CHANGELOG.md b/analyzer/CHANGELOG.md
index 7697be2..6581517 100644
--- a/analyzer/CHANGELOG.md
+++ b/analyzer/CHANGELOG.md
@@ -1,3 +1,22 @@
+## 5.10.0
+* Added `DartType.isDartCoreType`.
+
+## 5.9.0
+* Deprecated `FunctionBody.isPotentiallyMutatedInClosure`, not used by clients.
+* Fix for `FunctionBody.isPotentiallyMutatedInScope` and pattern assignment.
+
+## 5.8.0
+* Deprecated `DartType.isVoid`, use `is VoidType` instead.
+* `records`, `patterns`, and `class-modifiers` features enabled by default.
+
+## 5.7.1
+* Require SDK `>=2.19.0 <3.0.0` to use `PathNotFoundException` from `dart:io`.
+
+## 5.7.0
+* Work on `class-modifiers` feature.
+* Work on `patterns` feature.
+* Support for primitive equality.
+
 ## 5.6.0
 * Fixes for patterns parsing.
 * Implemented `DartPattern.precedence`.
diff --git a/analyzer/TRIAGE.md b/analyzer/TRIAGE.md
index 8b039c1..83bb51b 100644
--- a/analyzer/TRIAGE.md
+++ b/analyzer/TRIAGE.md
@@ -58,10 +58,10 @@
 
 ### P2
 
-* Incorrect analysis errors or warnings, on edge cases with simple workaround
+* Incorrect analysis errors, on edge cases with simple workaround
   * EXAMPLE: Disabling the error or warning 'fixes' the issue and unblocks
     users.
-* Incorrect analysis infos/hints, on edge cases
+* Incorrect analysis warnings, on edge cases
 * Incorrect resolution of symbols or libraries, edge cases only with workarounds
 * Incorrect data from analyzer API, edge cases without workaround
 * Automation resulting in incorrect code, edge cases
@@ -75,8 +75,8 @@
 
 * Uncaught exceptions caught by a fuzzer, but believed to be theoretical
   situations only
-* Incorrect analysis errors or warnings, theoretical
-* Incorrect analysis infos/hints, on edge cases with workaround
+* Incorrect analysis errors, theoretical
+* Incorrect analysis warnings, on edge cases with workaround
 * Incorrect resolution of symbols or libraries, theoretical
 * Incorrect data from analyzer API, edge case with workaround available
 * Performance regression impacting edge cases with workaround or without
@@ -89,7 +89,7 @@
 
 ### P4
 
-* Incorrect analysis infos/hints, theoretical
+* Incorrect analysis warnings, theoretical
 * Incorrect data from analyzer API, theoretical
 * Theoretical performance problems
 * An enhancement that may have some evidence that it isn't a good idea to
@@ -122,8 +122,7 @@
 * "corrupted code" - Modification of source code in such a way that it is
   more than just a bit wrong or having some symbols that don't exist, but is
   not valid Dart and would be painful to manually correct.
-* "diagnostic" - An error, warning, hint, or lint generated by the analyzer
-  or linter.
+* "diagnostic" - An error, warning, or lint generated by the analyzer.
 * "incorrect code" - Modification of code in a way that is known to be wrong,
   but would be trivial to figure out how to fix for the human using the tool.
 * "key users" - Flutter, Pub, Fuchsia, Dart, Google/1P
diff --git a/analyzer/doc/implementation/pub_diagnostics.md b/analyzer/doc/implementation/pub_diagnostics.md
new file mode 100644
index 0000000..26d1083
--- /dev/null
+++ b/analyzer/doc/implementation/pub_diagnostics.md
@@ -0,0 +1,53 @@
+# Adding a new pubspec diagnostic
+
+This document describes the process of adding a new (non-lint) pubspec
+diagnostic to the analyzer.
+
+## Background
+
+Analyzer parses pubspecs and sends change notifications to a validator that can
+be used to produce diagnostics in pubspec.yaml files. Taking advantage of this
+we can provide rich dynamic feedback to authors as they type.
+
+## Recipe
+
+The basic recipe for implementing a new pubspec diagnostic is as follows:
+
+1. Introduce a new `PubspecWarningCode` code to `messages.yaml`.
+2. Re-generate Dart error code files (run `generate_files`).
+3. Add corresponding tests to a new test library in
+   `test/src/pubspec/diagnostics`.
+4. Implement analysis in a new validator in `lib/src/pubspec/validators` and
+   invoke it from PubspecValidator (or enhance an existing one).
+
+Once implemented, you’ll want to look for ecosystem breakages. Useful bots to
+watch:
+
+* [analyzer-analysis-server-linux-try][]
+  analyzes SDK packages and is a great early warning system
+* [flutter-analyze-try][] and
+* [flutter-engine-linux-try][]
+  will tell you if your change will block an SDK roll into Flutter
+
+[analyzer-analysis-server-linux-try](https://ci.chromium.org/p/dart/builders/ci.sandbox/analyzer-analysis-server-linux)
+[flutter-analyze-try](https://ci.chromium.org/p/dart/builders/ci.sandbox/flutter-analyze)
+[flutter-engine-linux-try](https://ci.chromium.org/p/dart/builders/ci.sandbox/flutter-engine-linux)
+
+You’ll need to clean up these downstream breakages before you can land yours.
+
+In the case of SDK breakages, you can fix them in your initial PR. Flutter and
+Flutter Engine breakages should be handled in PRs to their respective Flutter
+repos.
+
+## Example: Deprecated Fields
+
+The introduction of diagnostics for deprecated fields (corresponding to the
+existing [pub client check][]) demonstrates a lot of these ideas and serves as a
+good jumping off point for future diagnostics.
+
+[pub client check](https://github.com/dart-lang/pub/blob/ab41ef0aaef7a20f759c6147aa8121a1396ee589/lib/src/validator/deprecated_fields.dart#L18-L35)
+
+1. The initial PR: https://dart-review.googlesource.com/c/sdk/+/204420 (notice 
+   the breakage to analyzer_plugin and telemetry that needed fixing).
+2. Flutter repo fixes: https://github.com/flutter/flutter/pull/84997 and 
+   https://github.com/flutter/flutter/pull/85036.
diff --git a/analyzer/example/ddd01.dart b/analyzer/example/ddd01.dart
index 8130a93..4d37453 100644
--- a/analyzer/example/ddd01.dart
+++ b/analyzer/example/ddd01.dart
@@ -1,14 +1,18 @@
-import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
 
 void main() async {
-  var path = '/Users/scheglov/dart/issue50962/br_table.0.dart';
-  var collection = AnalysisContextCollectionImpl(includedPaths: [
-    path,
+  final collection = AnalysisContextCollectionImpl(includedPaths: [
+    '/Users/scheglov/dart/test',
   ]);
-  var analysisContext = collection.contextFor(path);
-  var unitResult = await analysisContext.currentSession.getResolvedUnit(path);
-  unitResult as ResolvedUnitResult;
 
-  // await Future<void>.delayed(const Duration(days: 1));
+  for (final analysisContext in collection.contexts) {
+    // print(analysisContext.contextRoot.root.path);
+    final analysisSession = analysisContext.currentSession;
+    for (final path in analysisContext.contextRoot.analyzedFiles()) {
+      if (path.endsWith('.dart')) {
+        print('  $path');
+        await analysisSession.getResolvedUnit(path);
+      }
+    }
+  }
 }
diff --git a/analyzer/example/ddd03.dart b/analyzer/example/ddd03.dart
index f8b3c89..eb77cca 100644
--- a/analyzer/example/ddd03.dart
+++ b/analyzer/example/ddd03.dart
@@ -26,8 +26,8 @@
       includedPaths: [
         path,
       ],
-      packagesFile:
-          '/Users/scheglov/dart/flutter_plugins/packages/camera/camera/.dart_tool/package_config.json',
+      // packagesFile:
+      //     '/Users/scheglov/dart/flutter_plugins/packages/camera/camera/.dart_tool/package_config.json',
     );
 
     // print('[Analysis contexts: ${collection.contexts.length}]');
diff --git a/analyzer/example/ddd03_2.dart b/analyzer/example/ddd03_2.dart
new file mode 100644
index 0000000..5706860
--- /dev/null
+++ b/analyzer/example/ddd03_2.dart
@@ -0,0 +1,161 @@
+// import 'dart:convert';
+// import 'dart:io';
+//
+// import 'package:analyzer/file_system/physical_file_system.dart';
+// import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
+// import 'package:analyzer/src/dart/analysis/byte_store.dart';
+// import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
+// import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart';
+// import 'package:vm_service/vm_service.dart';
+//
+// import 'heap/analysis.dart';
+// import 'heap/format.dart';
+// import 'heap/load.dart';
+//
+// void main() async {
+//   var path = '/Users/scheglov/dart/flutter_plugins/packages/camera';
+//   // var path = '/Users/scheglov/dart/flutter_plugins/packages';
+//
+//   while (true) {
+//     var resourceProvider = PhysicalResourceProvider.INSTANCE;
+//     var fileContentCache = FileContentCache(resourceProvider);
+//     var unlinkedUnitStore = UnlinkedUnitStoreImpl();
+//
+//     var collection = AnalysisContextCollectionImpl(
+//       byteStore: MemoryByteStore(),
+//       resourceProvider: resourceProvider,
+//       fileContentCache: fileContentCache,
+//       unlinkedUnitStore: unlinkedUnitStore,
+//       sdkPath: '/Users/scheglov/Applications/dart-sdk',
+//       // performanceLog: PerformanceLog(stdout),
+//       includedPaths: [
+//         path,
+//       ],
+//       // packagesFile:
+//       //     '/Users/scheglov/dart/flutter_plugins/packages/camera/camera/.dart_tool/package_config.json',
+//     );
+//
+//     // print('[Analysis contexts: ${collection.contexts.length}]');
+//
+//     var timer = Stopwatch()..start();
+//     for (var analysisContext in collection.contexts) {
+//       // print(analysisContext.contextRoot.root.path);
+//       for (var filePath in analysisContext.contextRoot.analyzedFiles()) {
+//         if (filePath.endsWith('.dart')) {
+//           // print('  $filePath');
+//           var analysisSession = analysisContext.currentSession;
+//           await analysisSession.getResolvedUnit(filePath);
+//         }
+//       }
+//     }
+//     timer.stop();
+//     print('[time: ${timer.elapsedMilliseconds} ms]');
+//
+//     {
+//       var timer = Stopwatch()..start();
+//       var chunks = await loadFromUri(Uri.parse('http://127.0.0.1:5000'));
+//       // final length = chunks
+//       //     .map((e) => e.lengthInBytes)
+//       //     .fold<int>(0, (prev, e) => prev + e);
+//       // print(
+//       //   '  [${timer.elapsedMilliseconds} ms] '
+//       //   'Downloaded heap snapshot, ${length / 1024 / 1024} MB.',
+//       // );
+//
+//       final graph = HeapSnapshotGraph.fromChunks(chunks);
+//       print('  [${timer.elapsedMilliseconds} ms] Created HeapSnapshotGraph.');
+//       print('  externalSize: ${graph.externalSize}');
+//       print('  shallowSize: ${graph.shallowSize}');
+//       print('  Objects: ${graph.objects.length}');
+//
+//       final analysis = Analysis(graph);
+//       print('  [${timer.elapsedMilliseconds} ms] Created Analysis.');
+//
+//       {
+//         print('All objects.');
+//         final objects = analysis.reachableObjects;
+//         final stats = analysis.generateObjectStats(objects);
+//         print(formatHeapStats(stats, maxLines: 20));
+//         print('');
+//       }
+//
+//       {
+//         print('ElementImpl(s)');
+//         var fileStateList = analysis.filter(
+//           analysis.reachableObjects,
+//           (object) {
+//             return object.klass.name.endsWith('ElementImpl');
+//           },
+//         );
+//         analysis.printObjectStats(fileStateList);
+//         print('');
+//         // final allObjects = analysis.transitiveGraph(fileStateList);
+//         // analysis.printObjectStats(allObjects);
+//         // print('');
+//       }
+//
+//       {
+//         print('Version(s)');
+//         var objectList = analysis.filter(
+//           analysis.reachableObjects,
+//           (object) {
+//             return object.klass.name == 'Version';
+//           },
+//         );
+//         analysis.printObjectStats(objectList);
+//         print('');
+//         // final allObjects = analysis.transitiveGraph(fileStateList);
+//         // analysis.printObjectStats(allObjects);
+//         // print('');
+//
+//         const maxEntries = 10;
+//         final paths = analysis.retainingPathsOf(objectList, 10);
+//         for (int i = 0; i < paths.length; ++i) {
+//           if (maxEntries != -1 && i >= maxEntries) break;
+//           final path = paths[i];
+//           print('There are ${path.count} retaining paths of');
+//           print(formatRetainingPath(analysis.graph, paths[i]));
+//           print('');
+//         }
+//       }
+//     }
+//   }
+//
+//   // var analysisContext = collection.contextFor(path);
+//   // var unitResult = await analysisContext.currentSession.getResolvedUnit(path);
+//   // unitResult as ResolvedUnitResult;
+//
+//   // await Future<void>.delayed(const Duration(days: 1));
+// }
+//
+// extension on Analysis {
+//   void printObjectStats(IntSet objectIds) {
+//     final stats = generateObjectStats(objectIds);
+//     print(formatHeapStats(stats, maxLines: 20));
+//     print('');
+//   }
+//
+//   void printRetainers(
+//     IntSet objectIds, {
+//     int maxEntries = 3,
+//   }) {
+//     final paths = retainingPathsOf(objectIds, 20);
+//     for (int i = 0; i < paths.length; ++i) {
+//       if (i >= maxEntries) break;
+//       final path = paths[i];
+//       print('There are ${path.count} retaining paths of');
+//       print(formatRetainingPath(graph, paths[i]));
+//       print('');
+//     }
+//   }
+//
+//   IntSet filterByClass(
+//     IntSet objectIds, {
+//     required Uri libraryUri,
+//     required String name,
+//   }) {
+//     return filter(reachableObjects, (object) {
+//       return object.klass.libraryUri == libraryUri && object.klass.name == name;
+//     });
+//   }
+// }
diff --git a/analyzer/example/ddd04.dart b/analyzer/example/ddd04.dart
index 0a21f3f..881560c 100644
--- a/analyzer/example/ddd04.dart
+++ b/analyzer/example/ddd04.dart
@@ -15,9 +15,9 @@
 // /// --observe:5000 --disable-service-auth-codes --pause_isolates_on_unhandled_exceptions=false --pause_isolates_on_exit=false
 // void main() async {
 //   // var path = '/Users/scheglov/dart/rwf-materials';
-//   // var path = '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment';
-//   var path =
-//       '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment/projects/starter/packages/component_library';
+//   var path = '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment';
+//   // var path =
+//   //     '/Users/scheglov/dart/rwf-materials/01-setting-up-your-environment/projects/starter/packages/component_library';
 //
 //   // var profiler = ProcessProfiler.getProfilerForPlatform()!;
 //   while (true) {
@@ -74,151 +74,151 @@
 //     timer.stop();
 //     print('[time: ${timer.elapsedMilliseconds} ms]');
 //
-//     {
-//       var timer = Stopwatch()..start();
-//       var chunks = await loadFromUri(Uri.parse('http://127.0.0.1:5000'));
-//       // final length = chunks
-//       //     .map((e) => e.lengthInBytes)
-//       //     .fold<int>(0, (prev, e) => prev + e);
-//       // print(
-//       //   '  [${timer.elapsedMilliseconds} ms] '
-//       //   'Downloaded heap snapshot, ${length / 1024 / 1024} MB.',
-//       // );
-//
-//       if (0 == 1) {
-//         final bytesBuilder = BytesBuilder();
-//         for (final chunk in chunks) {
-//           bytesBuilder.add(
-//             chunk.buffer.asUint8List(
-//               chunk.offsetInBytes,
-//               chunk.lengthInBytes,
-//             ),
-//           );
-//         }
-//         final bytes = bytesBuilder.toBytes();
-//         final path = '/Users/scheglov/tmp/01.heap_snapshot';
-//         File(path).writeAsBytesSync(bytes);
-//         final lengthStr = (bytes.length / 1024 / 1024).toStringAsFixed(2);
-//         print('Stored $lengthStr MB into $path');
-//       }
-//
-//       final graph = HeapSnapshotGraph.fromChunks(chunks);
-//       print('  [${timer.elapsedMilliseconds} ms] Created HeapSnapshotGraph.');
-//       print('  externalSize: ${graph.externalSize}');
-//       print('  shallowSize: ${graph.shallowSize}');
-//       print('  Objects: ${graph.objects.length}');
-//
-//       final analysis = Analysis(graph);
-//       print('  [${timer.elapsedMilliseconds} ms] Created Analysis.');
-//
-//       {
-//         print('All objects.');
-//         final objects = analysis.reachableObjects;
-//         final stats = analysis.generateObjectStats(objects);
-//         print(formatHeapStats(stats, maxLines: 20));
-//         print('');
-//       }
-//
-//       {
-//         print('FileState(s)');
-//         var fileStateList = analysis.filter(
-//           analysis.reachableObjects,
-//               (object) {
-//             return object.klass.name == 'FileState';
-//           },
-//         );
-//         analysis.printObjectStats(fileStateList);
-//         print('');
-//         final allObjects = analysis.transitiveGraph(fileStateList);
-//         analysis.printObjectStats(allObjects);
-//         print('');
-//       }
-//
-//       if (0 == 1) {
-//         print('Instances of: _SimpleUri');
-//         final uriList = analysis.filterByClassPatterns(
-//           analysis.reachableObjects,
-//           ['_SimpleUri'],
-//         );
-//         final stats = analysis.generateObjectStats(uriList);
-//         print(formatHeapStats(stats, maxLines: 20));
-//         print('');
-//
-//         final uriStringList = analysis.findReferences(uriList, [':_uri']);
-//
-//         // TODO(scheglov) Restore
-//         final uniqueUriStrSet = Set<String>();
-//         for (final objectId in uriStringList) {
-//           var object = graph.objects[objectId];
-//           var uriStr = object.data as String;
-//           if (!uniqueUriStrSet.add(uriStr)) {
-//             throw StateError('Duplicate URI: $uriStr');
-//           }
-//         }
-//
-//         final dstats = analysis.generateDataStats(uriStringList);
-//         print(formatDataStats(dstats, maxLines: 20));
-//       }
-//
-//       if (0 == 0) {
-//         print('Instances of: LibraryElementImpl');
-//         final uriList = analysis.filterByClassPatterns(
-//           analysis.reachableObjects,
-//           ['LibraryElementImpl'],
-//         );
-//         final stats = analysis.generateObjectStats(uriList);
-//         print(formatHeapStats(stats, maxLines: 20));
-//         print('');
-//       }
-//
-//       if (0 == 0) {
-//         print('Instances of: _GrowableList');
-//         final objectList = analysis.filter(analysis.reachableObjects, (object) {
-//           return object.klass.libraryUri == Uri.parse('dart:core') &&
-//               object.klass.name == '_GrowableList';
-//           // return analysis.variableLengthOf(object) == 0;
-//         });
-//
-//         // final objectList = analysis.filterByClassPatterns(
-//         //   analysis.reachableObjects,
-//         //   ['_GrowableList'],
-//         // );
-//         final stats = analysis.generateObjectStats(objectList);
-//         print(formatHeapStats(stats, maxLines: 20));
-//         print('');
-//
-//         const maxEntries = 10;
-//         final paths = analysis.retainingPathsOf(objectList, 10);
-//         for (int i = 0; i < paths.length; ++i) {
-//           if (maxEntries != -1 && i >= maxEntries) break;
-//           final path = paths[i];
-//           print('There are ${path.count} retaining paths of');
-//           print(formatRetainingPath(analysis.graph, paths[i]));
-//           print('');
-//         }
-//
-//         {
-//           print('Instances of empty: _GrowableList');
-//           final emptyList = analysis.filter(objectList, (object) {
-//             return analysis.variableLengthOf(object) == 0;
-//           });
-//           final stats = analysis.generateObjectStats(emptyList);
-//           print(formatHeapStats(stats, maxLines: 20));
-//           print('');
-//
-//           // final paths = analysis.retainingPathsOf(emptyList, 10);
-//           // for (int i = 0; i < paths.length; ++i) {
-//           //   if (maxEntries != -1 && i >= maxEntries) break;
-//           //   final path = paths[i];
-//           //   print('There are ${path.count} retaining paths of');
-//           //   print(formatRetainingPath(analysis.graph, paths[i]));
-//           //   print('');
-//           // }
-//         }
-//         // final dstats = analysis.generateDataStats(uriStringList);
-//         // print(formatDataStats(dstats, maxLines: 20));
-//       }
-//     }
+//     // {
+//     //   var timer = Stopwatch()..start();
+//     //   var chunks = await loadFromUri(Uri.parse('http://127.0.0.1:5000'));
+//     //   // final length = chunks
+//     //   //     .map((e) => e.lengthInBytes)
+//     //   //     .fold<int>(0, (prev, e) => prev + e);
+//     //   // print(
+//     //   //   '  [${timer.elapsedMilliseconds} ms] '
+//     //   //   'Downloaded heap snapshot, ${length / 1024 / 1024} MB.',
+//     //   // );
+//     //
+//     //   if (0 == 1) {
+//     //     final bytesBuilder = BytesBuilder();
+//     //     for (final chunk in chunks) {
+//     //       bytesBuilder.add(
+//     //         chunk.buffer.asUint8List(
+//     //           chunk.offsetInBytes,
+//     //           chunk.lengthInBytes,
+//     //         ),
+//     //       );
+//     //     }
+//     //     final bytes = bytesBuilder.toBytes();
+//     //     final path = '/Users/scheglov/tmp/01.heap_snapshot';
+//     //     File(path).writeAsBytesSync(bytes);
+//     //     final lengthStr = (bytes.length / 1024 / 1024).toStringAsFixed(2);
+//     //     print('Stored $lengthStr MB into $path');
+//     //   }
+//     //
+//     //   final graph = HeapSnapshotGraph.fromChunks(chunks);
+//     //   print('  [${timer.elapsedMilliseconds} ms] Created HeapSnapshotGraph.');
+//     //   print('  externalSize: ${graph.externalSize}');
+//     //   print('  shallowSize: ${graph.shallowSize}');
+//     //   print('  Objects: ${graph.objects.length}');
+//     //
+//     //   final analysis = Analysis(graph);
+//     //   print('  [${timer.elapsedMilliseconds} ms] Created Analysis.');
+//     //
+//     //   {
+//     //     print('All objects.');
+//     //     final objects = analysis.reachableObjects;
+//     //     final stats = analysis.generateObjectStats(objects);
+//     //     print(formatHeapStats(stats, maxLines: 20));
+//     //     print('');
+//     //   }
+//     //
+//     //   {
+//     //     print('FileState(s)');
+//     //     var fileStateList = analysis.filter(
+//     //       analysis.reachableObjects,
+//     //           (object) {
+//     //         return object.klass.name == 'FileState';
+//     //       },
+//     //     );
+//     //     analysis.printObjectStats(fileStateList);
+//     //     print('');
+//     //     final allObjects = analysis.transitiveGraph(fileStateList);
+//     //     analysis.printObjectStats(allObjects);
+//     //     print('');
+//     //   }
+//     //
+//     //   if (0 == 1) {
+//     //     print('Instances of: _SimpleUri');
+//     //     final uriList = analysis.filterByClassPatterns(
+//     //       analysis.reachableObjects,
+//     //       ['_SimpleUri'],
+//     //     );
+//     //     final stats = analysis.generateObjectStats(uriList);
+//     //     print(formatHeapStats(stats, maxLines: 20));
+//     //     print('');
+//     //
+//     //     final uriStringList = analysis.findReferences(uriList, [':_uri']);
+//     //
+//     //     // TODO(scheglov) Restore
+//     //     final uniqueUriStrSet = Set<String>();
+//     //     for (final objectId in uriStringList) {
+//     //       var object = graph.objects[objectId];
+//     //       var uriStr = object.data as String;
+//     //       if (!uniqueUriStrSet.add(uriStr)) {
+//     //         throw StateError('Duplicate URI: $uriStr');
+//     //       }
+//     //     }
+//     //
+//     //     final dstats = analysis.generateDataStats(uriStringList);
+//     //     print(formatDataStats(dstats, maxLines: 20));
+//     //   }
+//     //
+//     //   if (0 == 0) {
+//     //     print('Instances of: LibraryElementImpl');
+//     //     final uriList = analysis.filterByClassPatterns(
+//     //       analysis.reachableObjects,
+//     //       ['LibraryElementImpl'],
+//     //     );
+//     //     final stats = analysis.generateObjectStats(uriList);
+//     //     print(formatHeapStats(stats, maxLines: 20));
+//     //     print('');
+//     //   }
+//     //
+//     //   if (0 == 0) {
+//     //     print('Instances of: _GrowableList');
+//     //     final objectList = analysis.filter(analysis.reachableObjects, (object) {
+//     //       return object.klass.libraryUri == Uri.parse('dart:core') &&
+//     //           object.klass.name == '_GrowableList';
+//     //       // return analysis.variableLengthOf(object) == 0;
+//     //     });
+//     //
+//     //     // final objectList = analysis.filterByClassPatterns(
+//     //     //   analysis.reachableObjects,
+//     //     //   ['_GrowableList'],
+//     //     // );
+//     //     final stats = analysis.generateObjectStats(objectList);
+//     //     print(formatHeapStats(stats, maxLines: 20));
+//     //     print('');
+//     //
+//     //     const maxEntries = 10;
+//     //     final paths = analysis.retainingPathsOf(objectList, 10);
+//     //     for (int i = 0; i < paths.length; ++i) {
+//     //       if (maxEntries != -1 && i >= maxEntries) break;
+//     //       final path = paths[i];
+//     //       print('There are ${path.count} retaining paths of');
+//     //       print(formatRetainingPath(analysis.graph, paths[i]));
+//     //       print('');
+//     //     }
+//     //
+//     //     {
+//     //       print('Instances of empty: _GrowableList');
+//     //       final emptyList = analysis.filter(objectList, (object) {
+//     //         return analysis.variableLengthOf(object) == 0;
+//     //       });
+//     //       final stats = analysis.generateObjectStats(emptyList);
+//     //       print(formatHeapStats(stats, maxLines: 20));
+//     //       print('');
+//     //
+//     //       // final paths = analysis.retainingPathsOf(emptyList, 10);
+//     //       // for (int i = 0; i < paths.length; ++i) {
+//     //       //   if (maxEntries != -1 && i >= maxEntries) break;
+//     //       //   final path = paths[i];
+//     //       //   print('There are ${path.count} retaining paths of');
+//     //       //   print(formatRetainingPath(analysis.graph, paths[i]));
+//     //       //   print('');
+//     //       // }
+//     //     }
+//     //     // final dstats = analysis.generateDataStats(uriStringList);
+//     //     // print(formatDataStats(dstats, maxLines: 20));
+//     //   }
+//     // }
 //
 //     break;
 //
@@ -234,34 +234,34 @@
 //   // await Future<void>.delayed(const Duration(days: 1));
 // }
 //
-// extension on Analysis {
-//   void printObjectStats(IntSet objectIds) {
-//     final stats = generateObjectStats(objectIds);
-//     print(formatHeapStats(stats, maxLines: 20));
-//     print('');
-//   }
-//
-//   void printRetainers(
-//       IntSet objectIds, {
-//         int maxEntries = 3,
-//       }) {
-//     final paths = retainingPathsOf(objectIds, 20);
-//     for (int i = 0; i < paths.length; ++i) {
-//       if (i >= maxEntries) break;
-//       final path = paths[i];
-//       print('There are ${path.count} retaining paths of');
-//       print(formatRetainingPath(graph, paths[i]));
-//       print('');
-//     }
-//   }
-//
-//   IntSet filterByClass(
-//       IntSet objectIds, {
-//         required Uri libraryUri,
-//         required String name,
-//       }) {
-//     return filter(reachableObjects, (object) {
-//       return object.klass.libraryUri == libraryUri && object.klass.name == name;
-//     });
-//   }
-// }
+// // extension on Analysis {
+// //   void printObjectStats(IntSet objectIds) {
+// //     final stats = generateObjectStats(objectIds);
+// //     print(formatHeapStats(stats, maxLines: 20));
+// //     print('');
+// //   }
+// //
+// //   void printRetainers(
+// //       IntSet objectIds, {
+// //         int maxEntries = 3,
+// //       }) {
+// //     final paths = retainingPathsOf(objectIds, 20);
+// //     for (int i = 0; i < paths.length; ++i) {
+// //       if (i >= maxEntries) break;
+// //       final path = paths[i];
+// //       print('There are ${path.count} retaining paths of');
+// //       print(formatRetainingPath(graph, paths[i]));
+// //       print('');
+// //     }
+// //   }
+// //
+// //   IntSet filterByClass(
+// //       IntSet objectIds, {
+// //         required Uri libraryUri,
+// //         required String name,
+// //       }) {
+// //     return filter(reachableObjects, (object) {
+// //       return object.klass.libraryUri == libraryUri && object.klass.name == name;
+// //     });
+// //   }
+// // }
diff --git a/analyzer/lib/dart/analysis/analysis_options.dart b/analyzer/lib/dart/analysis/analysis_options.dart
index 05fbbaf..ddeab65 100644
--- a/analyzer/lib/dart/analysis/analysis_options.dart
+++ b/analyzer/lib/dart/analysis/analysis_options.dart
@@ -35,8 +35,8 @@
   /// analysis.
   List<String> get excludePatterns;
 
-  /// Return `true` if analysis is to generate hint results (e.g. type inference
-  /// based information and pub best practices).
+  /// Return `true` if analysis is to generate hint results (e.g. best practices
+  /// and analysis based on certain annotations).
   bool get hint;
 
   /// Return `true` if analysis is to generate lint warnings.
diff --git a/analyzer/lib/dart/ast/ast.dart b/analyzer/lib/dart/ast/ast.dart
index a146d77..307462a 100644
--- a/analyzer/lib/dart/ast/ast.dart
+++ b/analyzer/lib/dart/ast/ast.dart
@@ -41,9 +41,6 @@
 import 'package:analyzer/src/generated/source.dart' show LineInfo;
 import 'package:meta/meta.dart';
 
-@Deprecated('Use PatternField and visitPatternField() instead')
-typedef RecordPatternField = PatternField;
-
 /// Two or more string literals that are implicitly concatenated because of
 /// being adjacent (separated only by whitespace).
 ///
@@ -1011,6 +1008,10 @@
   @override
   ImplementsClause? get implementsClause;
 
+  /// Return the 'inline' keyword, or `null` if the keyword was absent.
+  @experimental
+  Token? get inlineKeyword;
+
   /// Returns the left curly bracket.
   @override
   Token get leftBracket;
@@ -1959,6 +1960,9 @@
   /// not be resolved.
   ConstructorElement? get constructorElement;
 
+  @override
+  FieldElement? get declaredElement;
+
   /// Return the name of the constant.
   Token get name;
 
@@ -2687,6 +2691,7 @@
   /// level function or method containing this [FunctionBody], return `false`.
   ///
   /// Throws an exception if resolution has not yet been performed.
+  @Deprecated('Not used by clients')
   bool isPotentiallyMutatedInClosure(VariableElement variable);
 
   /// If [variable] is a local variable or parameter declared anywhere within
@@ -4277,8 +4282,13 @@
 /// Clients may not extend, implement or mix-in this class.
 @experimental
 abstract class PatternField implements AstNode {
-  /// The element referenced explicitly by [name], or implicitly by the
-  /// variable pattern inside [pattern]. Is `null` if not resolved yet,
+  /// The name specified explicitly by [name], or implied by the variable
+  /// pattern inside [pattern]. Always `null` if [name] is `null`. Can be `null`
+  /// if [name] does not have the explicit name and [pattern] is not a variable
+  /// pattern.
+  String? get effectiveName;
+
+  /// The element referenced by [effectiveName]. Is `null` if not resolved yet,
   /// not `null` inside valid [ObjectPattern]s, always `null` inside
   /// [RecordPattern]s.
   Element? get element;
diff --git a/analyzer/lib/dart/ast/visitor.dart b/analyzer/lib/dart/ast/visitor.dart
index 770a231..42064b5 100644
--- a/analyzer/lib/dart/ast/visitor.dart
+++ b/analyzer/lib/dart/ast/visitor.dart
@@ -2163,9 +2163,6 @@
   @override
   R? visitRecordPattern(RecordPattern node) => null;
 
-  @Deprecated('Use visitPatternField() instead')
-  void visitRecordPatternField(RecordPatternField node) {}
-
   @override
   R? visitRecordTypeAnnotation(RecordTypeAnnotation node) => null;
 
diff --git a/analyzer/lib/dart/element/element.dart b/analyzer/lib/dart/element/element.dart
index 4e9d67f..b56fe1e 100644
--- a/analyzer/lib/dart/element/element.dart
+++ b/analyzer/lib/dart/element/element.dart
@@ -200,11 +200,18 @@
   /// <i>abstract</i> is different from <i>has unimplemented members</i>.
   bool get isAbstract;
 
-  /// Return `true` if this class is a base class. A class is a base class if it
-  /// has an explicit `base` modifier.
+  /// Return `true` if this class is a base class.
+  ///
+  /// A class is a base class if it has an explicit `base` modifier, or the
+  /// class has a `base` induced modifier and [isSealed] is `true` as well.
+  /// The base modifier allows the class to be extended but not implemented.
   @experimental
   bool get isBase;
 
+  /// Return `true` if this class can be instantiated.
+  @experimental
+  bool get isConstructable;
+
   /// Return `true` if this class represents the class 'Enum' defined in the
   /// dart:core library.
   bool get isDartCoreEnum;
@@ -219,13 +226,21 @@
   @experimental
   bool get isExhaustive;
 
-  /// Return `true` if this class is a final class. A class is a final class if
-  /// it has an explicit `final` modifier.
+  /// Return `true` if this class is a final class.
+  ///
+  /// A class is a final class if it has an explicit `final` modifier, or the
+  /// class has a `final` induced modifier and [isSealed] is `true` as well.
+  /// The final modifier prohibits this class from being extended, implemented,
+  /// or mixed in.
   @experimental
   bool get isFinal;
 
-  /// Return `true` if this class is an interface class. A class is an interface
-  /// class if it has an explicit `interface` modifier.
+  /// Return `true` if this class is an interface class.
+  ///
+  /// A class is an interface class if it has an explicit `interface` modifier,
+  /// or the class has an `interface` induced modifier and [isSealed] is `true`
+  /// as well. The interface modifier allows the class to be implemented, but
+  /// not extended or mixed in.
   @experimental
   bool get isInterface;
 
@@ -724,6 +739,23 @@
   /// Return the analysis session in which this element is defined.
   AnalysisSession? get session;
 
+  /// The version where this SDK API was added.
+  ///
+  /// A `@Since()` annotation can be applied to a library declaration,
+  /// any public declaration in a library, or in a class, or to an optional
+  /// parameter, etc.
+  ///
+  /// The returned version is "effective", so that if a library is annotated
+  /// then all elements of the library inherit it; or if a class is annotated
+  /// then all members and constructors of the class inherit it.
+  ///
+  /// If multiple `@Since()` annotations apply to the same element, the latest
+  /// version takes precedence.
+  ///
+  /// Returns `null` if the element is not declared in SDK, or does not have
+  /// a `@Since()` annotation applicable to it.
+  Version? get sinceSdkVersion;
+
   @override
   Source? get source;
 
@@ -1986,8 +2018,11 @@
   /// Returns the result of applying augmentations to this element.
   AugmentedMixinElement get augmented;
 
-  /// Return `true` if this mixin is a base mixin. A mixin is a base mixin if it
-  /// has an explicit `base` modifier.
+  /// Return `true` if this mixin is a base mixin.
+  ///
+  /// A mixin is a base mixin if it has an explicit `base` modifier, or the
+  /// mixin has a `base` induced modifier and [isSealed] is `true` as well.
+  /// The base modifier allows a mixin to be mixed in but not implemented.
   @experimental
   bool get isBase;
 
diff --git a/analyzer/lib/dart/element/type.dart b/analyzer/lib/dart/element/type.dart
index fcb6243..31fece1 100644
--- a/analyzer/lib/dart/element/type.dart
+++ b/analyzer/lib/dart/element/type.dart
@@ -118,10 +118,15 @@
   /// dart:core library.
   bool get isDartCoreSymbol;
 
+  /// Return `true` if this type represents the type 'Type' defined in the
+  /// dart:core library.
+  bool get isDartCoreType;
+
   /// Return `true` if this type represents the type 'dynamic'.
   bool get isDynamic;
 
   /// Return `true` if this type represents the type 'void'.
+  @Deprecated('Use `is VoidType` instead')
   bool get isVoid;
 
   /// Return the name of this type, or `null` if the type does not have a name,
diff --git a/analyzer/lib/error/listener.dart b/analyzer/lib/error/listener.dart
index ee23979..44e7701 100644
--- a/analyzer/lib/error/listener.dart
+++ b/analyzer/lib/error/listener.dart
@@ -73,10 +73,10 @@
   /// Report an error with the given [errorCode] and [arguments]. The [element]
   /// is used to compute the location of the error.
   void reportErrorForElement(ErrorCode errorCode, Element element,
-      [List<Object>? arguments]) {
+      [List<Object>? arguments, List<DiagnosticMessage>? messages]) {
     var nonSynthetic = element.nonSynthetic;
-    reportErrorForOffset(
-        errorCode, nonSynthetic.nameOffset, nonSynthetic.nameLength, arguments);
+    reportErrorForOffset(errorCode, nonSynthetic.nameOffset,
+        nonSynthetic.nameLength, arguments, messages);
   }
 
   /// Report a diagnostic with the given [code] and [arguments]. The
diff --git a/analyzer/lib/file_system/file_system.dart b/analyzer/lib/file_system/file_system.dart
index e0f9c23..1d38d61 100644
--- a/analyzer/lib/file_system/file_system.dart
+++ b/analyzer/lib/file_system/file_system.dart
@@ -149,6 +149,15 @@
   ResourceWatcher watch();
 }
 
+/// Exception thrown when a file operation fails because a file or directory
+/// does not exist.
+class PathNotFoundException extends FileSystemException {
+  PathNotFoundException(super.path, super.message);
+
+  @override
+  String toString() => 'PathNotFoundException(path=$path; message=$message)';
+}
+
 /// The abstract class [Resource] is an abstraction of file or folder.
 abstract class Resource {
   /// Return `true` if this resource exists.
diff --git a/analyzer/lib/file_system/memory_file_system.dart b/analyzer/lib/file_system/memory_file_system.dart
index 4f6a36f..c338d10 100644
--- a/analyzer/lib/file_system/memory_file_system.dart
+++ b/analyzer/lib/file_system/memory_file_system.dart
@@ -33,6 +33,11 @@
   @visibleForTesting
   final Duration? delayWatcherInitialization;
 
+  /// Paths that should have `PathNotFoundException`s emitted on their watch
+  /// streams.
+  @visibleForTesting
+  final Set<String> emitPathNotFoundExceptionsForPaths = {};
+
   MemoryResourceProvider({
     pathos.Context? context,
     this.delayWatcherInitialization,
@@ -625,6 +630,10 @@
         }
       });
       ready.complete();
+      if (provider.emitPathNotFoundExceptionsForPaths.contains(path)) {
+        streamController.addError(PathNotFoundException(
+            path, 'Simulated PathNotFoundException from _MemoryResource'));
+      }
     }
 
     final delayWatcherInitialization = provider.delayWatcherInitialization;
diff --git a/analyzer/lib/file_system/physical_file_system.dart b/analyzer/lib/file_system/physical_file_system.dart
index 962862f..c87286a 100644
--- a/analyzer/lib/file_system/physical_file_system.dart
+++ b/analyzer/lib/file_system/physical_file_system.dart
@@ -2,6 +2,7 @@
 // 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' as io;
 import 'dart:typed_data';
 
@@ -183,7 +184,7 @@
   @override
   ResourceWatcher watch() {
     final watcher = FileWatcher(_entry.path);
-    return ResourceWatcher(watcher.events, watcher.ready);
+    return ResourceWatcher(_wrapWatcherStream(watcher.events), watcher.ready);
   }
 
   @override
@@ -319,7 +320,7 @@
             // Don't suppress "Directory watcher closed," so the outer
             // listener can see the interruption & act on it.
             !error.message.startsWith("Directory watcher closed unexpectedly"));
-    return ResourceWatcher(events, watcher.ready);
+    return ResourceWatcher(_wrapWatcherStream(events), watcher.ready);
   }
 }
 
@@ -408,6 +409,28 @@
   }
 
   FileSystemException _wrapException(io.FileSystemException e) {
-    return FileSystemException(e.path ?? path, e.message);
+    if (e is io.PathNotFoundException) {
+      return PathNotFoundException(e.path ?? path, e.message);
+    } else {
+      return FileSystemException(e.path ?? path, e.message);
+    }
+  }
+
+  /// Wraps a `Stream<WatchEvent>` to map all known errors through
+  /// [_wrapException] into server types.
+  Stream<WatchEvent> _wrapWatcherStream(Stream<WatchEvent> original) {
+    /// Helper to map thrown `FileSystemException`s to servers abstraction.
+    Object mapException(Object e) {
+      return e is io.FileSystemException ? _wrapException(e) : e;
+    }
+
+    final mappedEventsController = StreamController<WatchEvent>();
+    final subscription = original.listen(
+      mappedEventsController.add,
+      onError: (Object e) => mappedEventsController.addError(mapException(e)),
+      onDone: () => mappedEventsController.close(),
+    );
+    mappedEventsController.onCancel = subscription.cancel;
+    return mappedEventsController.stream;
   }
 }
diff --git a/analyzer/lib/source/error_processor.dart b/analyzer/lib/source/error_processor.dart
index 207f15a..e34dad3 100644
--- a/analyzer/lib/source/error_processor.dart
+++ b/analyzer/lib/source/error_processor.dart
@@ -24,7 +24,7 @@
   /// Create an error config for the given error code map.
   /// For example:
   ///     new ErrorConfig({'missing_return' : 'error'});
-  /// will create a processor config that turns `missing_return` hints into
+  /// will create a processor config that turns `missing_return` warnings into
   /// errors.
   ErrorConfig(YamlNode? codeMap) {
     _processMap(codeMap);
diff --git a/analyzer/lib/src/dart/analysis/byte_store.dart b/analyzer/lib/src/dart/analysis/byte_store.dart
index ea166eb..c440b5c 100644
--- a/analyzer/lib/src/dart/analysis/byte_store.dart
+++ b/analyzer/lib/src/dart/analysis/byte_store.dart
@@ -24,10 +24,12 @@
 
   /// Associate [bytes] with [key].
   ///
-  /// If this store supports reference counting, returns the internalized
-  /// version of [bytes], the reference count is set to `1`.
-  ///
-  /// TODO(scheglov) Disable overwriting.
+  /// If this store supports reference counting:
+  /// 1. If there is already data with [key], increments the count and
+  ///    returns the existing data. This can happen when multiple isolates work
+  ///    with the same store (via native code).
+  /// 2. Otherwise, returns the internalized version of [bytes], the reference
+  ///    count is set to `1`.
   Uint8List putGet(String key, Uint8List bytes);
 
   /// If this store supports reference counting, decrements it for every key
@@ -40,6 +42,10 @@
   @visibleForTesting
   final Map<String, MemoryByteStoreEntry> map = {};
 
+  /// Throws [StateError] if [release] invoked when there is no entry.
+  @visibleForTesting
+  bool throwIfReleaseWithoutEntry = false;
+
   @override
   Uint8List? get(String key) {
     final entry = map[key];
@@ -53,6 +59,12 @@
 
   @override
   Uint8List putGet(String key, Uint8List bytes) {
+    final entry = map[key];
+    if (entry != null) {
+      entry.refCount++;
+      return entry.bytes;
+    }
+
     map[key] = MemoryByteStoreEntry._(bytes);
     return bytes;
   }
@@ -66,6 +78,8 @@
         if (entry.refCount == 0) {
           map.remove(key);
         }
+      } else if (throwIfReleaseWithoutEntry) {
+        throw StateError('No entry: $key');
       }
     }
   }
diff --git a/analyzer/lib/src/dart/analysis/driver.dart b/analyzer/lib/src/dart/analysis/driver.dart
index 690064c..25bfda6 100644
--- a/analyzer/lib/src/dart/analysis/driver.dart
+++ b/analyzer/lib/src/dart/analysis/driver.dart
@@ -87,7 +87,7 @@
 /// TODO(scheglov) Clean up the list of implicitly analyzed files.
 class AnalysisDriver implements AnalysisDriverGeneric {
   /// The version of data format, should be incremented on every format change.
-  static const int DATA_VERSION = 259;
+  static const int DATA_VERSION = 264;
 
   /// The number of exception contexts allowed to write. Once this field is
   /// zero, we stop writing any new exception contexts in this process.
diff --git a/analyzer/lib/src/dart/analysis/experiments.g.dart b/analyzer/lib/src/dart/analysis/experiments.g.dart
index 69bb459..8479b45 100644
--- a/analyzer/lib/src/dart/analysis/experiments.g.dart
+++ b/analyzer/lib/src/dart/analysis/experiments.g.dart
@@ -141,7 +141,7 @@
     isExpired: IsExpired.class_modifiers,
     documentation: 'Class modifiers',
     experimentalReleaseVersion: null,
-    releaseVersion: null,
+    releaseVersion: Version.parse('3.0.0'),
   );
 
   static final const_functions = ExperimentalFeature(
@@ -305,7 +305,7 @@
     isExpired: IsExpired.patterns,
     documentation: 'Patterns',
     experimentalReleaseVersion: null,
-    releaseVersion: null,
+    releaseVersion: Version.parse('3.0.0'),
   );
 
   static final records = ExperimentalFeature(
@@ -314,8 +314,8 @@
     isEnabledByDefault: IsEnabledByDefault.records,
     isExpired: IsExpired.records,
     documentation: 'Records',
-    experimentalReleaseVersion: Version.parse('2.19.0'),
-    releaseVersion: null,
+    experimentalReleaseVersion: null,
+    releaseVersion: Version.parse('3.0.0'),
   );
 
   static final sealed_class = ExperimentalFeature(
@@ -325,7 +325,7 @@
     isExpired: IsExpired.sealed_class,
     documentation: 'Sealed class',
     experimentalReleaseVersion: null,
-    releaseVersion: null,
+    releaseVersion: Version.parse('3.0.0'),
   );
 
   static final set_literals = ExperimentalFeature(
@@ -414,7 +414,7 @@
 /// enabled by default.
 class IsEnabledByDefault {
   /// Default state of the experiment "class-modifiers"
-  static const bool class_modifiers = false;
+  static const bool class_modifiers = true;
 
   /// Default state of the experiment "const-functions"
   static const bool const_functions = false;
@@ -462,13 +462,13 @@
   static const bool nonfunction_type_aliases = true;
 
   /// Default state of the experiment "patterns"
-  static const bool patterns = false;
+  static const bool patterns = true;
 
   /// Default state of the experiment "records"
-  static const bool records = false;
+  static const bool records = true;
 
   /// Default state of the experiment "sealed-class"
-  static const bool sealed_class = false;
+  static const bool sealed_class = true;
 
   /// Default state of the experiment "set-literals"
   static const bool set_literals = true;
diff --git a/analyzer/lib/src/dart/analysis/file_state.dart b/analyzer/lib/src/dart/analysis/file_state.dart
index 4aab0f0..5db207d 100644
--- a/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/analyzer/lib/src/dart/analysis/file_state.dart
@@ -690,6 +690,7 @@
       var bytes = driverUnlinkedUnit.toBytes();
       _fsState._byteStore.putGet(_unlinkedKey!, bytes);
       testData?.unlinkedKeyPut.add(unlinkedKey);
+      _fsState.unlinkedUnitStore.put(_unlinkedKey!, driverUnlinkedUnit);
       return driverUnlinkedUnit;
     });
   }
@@ -1196,6 +1197,9 @@
     // The removed file does not reference other files anymore.
     file._kind?.dispose();
 
+    // Release this unlinked data.
+    unlinkedUnitStore.release(file.unlinkedKey);
+
     // Recursively remove files that reference the removed file.
     for (var reference in file.referencingFiles.toList()) {
       changeFile(reference.path, removedFiles);
diff --git a/analyzer/lib/src/dart/analysis/index.dart b/analyzer/lib/src/dart/analysis/index.dart
index b5538b9..8179edc 100644
--- a/analyzer/lib/src/dart/analysis/index.dart
+++ b/analyzer/lib/src/dart/analysis/index.dart
@@ -294,9 +294,22 @@
     nameRelations.add(_NameRelationInfo(nameId, kind, offset, isQualified));
   }
 
-  void addPrefixForElement(PrefixElement prefixElement, Element element) {
+  /// Adds a prefix (or empty string for unprefixed) for an element.
+  void addPrefixForElement(Element element, {PrefixElement? prefix}) {
+    if (element is MultiplyDefinedElementImpl ||
+        // TODO(brianwilkerson) The last two conditions are here because the
+        //  elements for `dynamic` and `Never` are singletons and hence don't have
+        //  a parent element for which we can find an `_ElementInfo`. This means
+        //  that any reference to either type via a prefix can't be stored in the
+        //  index. The solution is to make those elements be normal (not unique)
+        //  elements.
+        element is DynamicElementImpl ||
+        element is NeverElementImpl) {
+      return;
+    }
+
     _ElementInfo elementInfo = _getElementInfo(element);
-    elementInfo.importPrefixes.add(prefixElement.name);
+    elementInfo.importPrefixes.add(prefix?.name ?? '');
   }
 
   void addSubtype(String name, List<String> members, List<String> supertypes) {
@@ -833,20 +846,10 @@
 
   @override
   void visitPrefixedIdentifier(PrefixedIdentifier node) {
-    var prefixElement = node.prefix.staticElement;
     var element = node.staticElement;
-    if (element != null &&
-        prefixElement is PrefixElement &&
-        element is! MultiplyDefinedElementImpl &&
-        element is! DynamicElementImpl &&
-        element is! NeverElementImpl) {
-      // TODO(brianwilkerson) The last two conditions are here because the
-      //  elements for `dynamic` and `Never` are singletons and hence don't have
-      //  a parent element for which we can find an `_ElementInfo`. This means
-      //  that any reference to either type via a prefix can't be stored in the
-      //  index. The solution is to make those elements be normal (not unique)
-      //  elements.
-      assembler.addPrefixForElement(prefixElement, element);
+    var prefixElement = node.prefix.staticElement;
+    if (element != null && prefixElement is PrefixElement) {
+      assembler.addPrefixForElement(element, prefix: prefixElement);
     }
     super.visitPrefixedIdentifier(node);
   }
@@ -886,6 +889,15 @@
       element = declaredParameterElement(node, element);
     }
 
+    final parent = node.parent;
+    if (element != null &&
+        element.enclosingElement is CompilationUnitElement &&
+        // We're only unprefixed when part of a PrefixedIdentifier if we're
+        // the left side.
+        (parent is! PrefixedIdentifier || parent.prefix == node)) {
+      assembler.addPrefixForElement(element);
+    }
+
     // record unresolved name reference
     bool isQualified = _isQualified(node);
     if (element == null) {
diff --git a/analyzer/lib/src/dart/analysis/library_analyzer.dart b/analyzer/lib/src/dart/analysis/library_analyzer.dart
index eede372..accd399 100644
--- a/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -250,7 +250,7 @@
     );
   }
 
-  /// Compute diagnostics in [units], including errors and warnings, hints,
+  /// Compute diagnostics in [units], including errors and warnings,
   /// lints, and a few other cases.
   void _computeDiagnostics(Map<FileState, CompilationUnitImpl> units) {
     units.forEach((file, unit) {
@@ -274,7 +274,7 @@
       }
       var usedElements = UsedLocalElements.merge(usedLocalElements);
       units.forEach((file, unit) {
-        _computeHints(
+        _computeWarnings(
           file,
           unit,
           usedImportedElements: usedImportedElements,
@@ -322,85 +322,6 @@
     }
   }
 
-  void _computeHints(
-    FileState file,
-    CompilationUnit unit, {
-    required List<UsedImportedElements> usedImportedElements,
-    required UsedLocalElements usedElements,
-  }) {
-    AnalysisErrorListener errorListener = _getErrorListener(file);
-    ErrorReporter errorReporter = _getErrorReporter(file);
-
-    if (!_libraryElement.isNonNullableByDefault) {
-      unit.accept(
-        LegacyDeadCodeVerifier(
-          errorReporter,
-          typeSystem: _typeSystem,
-        ),
-      );
-    }
-
-    UnicodeTextVerifier(errorReporter).verify(unit, file.content);
-
-    unit.accept(DeadCodeVerifier(errorReporter));
-
-    unit.accept(
-      BestPracticesVerifier(
-        errorReporter,
-        _typeProvider,
-        _libraryElement,
-        unit,
-        file.content,
-        declaredVariables: _declaredVariables,
-        typeSystem: _typeSystem,
-        inheritanceManager: _inheritance,
-        analysisOptions: _analysisOptions,
-        workspacePackage: _library.file.workspacePackage,
-      ),
-    );
-
-    unit.accept(OverrideVerifier(
-      _inheritance,
-      _libraryElement,
-      errorReporter,
-    ));
-
-    TodoFinder(errorReporter).findIn(unit);
-    LanguageVersionOverrideVerifier(errorReporter).verify(unit);
-
-    // Verify imports.
-    {
-      ImportsVerifier verifier = ImportsVerifier();
-      verifier.addImports(unit);
-      usedImportedElements.forEach(verifier.removeUsedElements);
-      verifier.generateDuplicateExportHints(errorReporter);
-      verifier.generateDuplicateImportHints(errorReporter);
-      verifier.generateDuplicateShownHiddenNameHints(errorReporter);
-      verifier.generateUnusedImportHints(errorReporter);
-      verifier.generateUnusedShownNameHints(errorReporter);
-      verifier.generateUnnecessaryImportHints(
-          errorReporter, usedImportedElements);
-    }
-
-    // Unused local elements.
-    {
-      UnusedLocalElementsVerifier visitor = UnusedLocalElementsVerifier(
-          errorListener, usedElements, _inheritance, _libraryElement);
-      unit.accept(visitor);
-    }
-
-    //
-    // Find code that uses features from an SDK version that does not satisfy
-    // the SDK constraints specified in analysis options.
-    //
-    var sdkVersionConstraint = _analysisOptions.sdkVersionConstraint;
-    if (sdkVersionConstraint != null) {
-      SdkConstraintVerifier verifier = SdkConstraintVerifier(
-          errorReporter, _libraryElement, _typeProvider, sdkVersionConstraint);
-      unit.accept(verifier);
-    }
-  }
-
   void _computeLints(
     FileState file,
     LinterContextUnit currentUnit,
@@ -478,6 +399,85 @@
     unit.accept(FfiVerifier(_typeSystem, errorReporter));
   }
 
+  void _computeWarnings(
+    FileState file,
+    CompilationUnit unit, {
+    required List<UsedImportedElements> usedImportedElements,
+    required UsedLocalElements usedElements,
+  }) {
+    AnalysisErrorListener errorListener = _getErrorListener(file);
+    ErrorReporter errorReporter = _getErrorReporter(file);
+
+    if (!_libraryElement.isNonNullableByDefault) {
+      unit.accept(
+        LegacyDeadCodeVerifier(
+          errorReporter,
+          typeSystem: _typeSystem,
+        ),
+      );
+    }
+
+    UnicodeTextVerifier(errorReporter).verify(unit, file.content);
+
+    unit.accept(DeadCodeVerifier(errorReporter));
+
+    unit.accept(
+      BestPracticesVerifier(
+        errorReporter,
+        _typeProvider,
+        _libraryElement,
+        unit,
+        file.content,
+        declaredVariables: _declaredVariables,
+        typeSystem: _typeSystem,
+        inheritanceManager: _inheritance,
+        analysisOptions: _analysisOptions,
+        workspacePackage: _library.file.workspacePackage,
+      ),
+    );
+
+    unit.accept(OverrideVerifier(
+      _inheritance,
+      _libraryElement,
+      errorReporter,
+    ));
+
+    TodoFinder(errorReporter).findIn(unit);
+    LanguageVersionOverrideVerifier(errorReporter).verify(unit);
+
+    // Verify imports.
+    {
+      ImportsVerifier verifier = ImportsVerifier();
+      verifier.addImports(unit);
+      usedImportedElements.forEach(verifier.removeUsedElements);
+      verifier.generateDuplicateExportWarnings(errorReporter);
+      verifier.generateDuplicateImportWarnings(errorReporter);
+      verifier.generateDuplicateShownHiddenNameWarnings(errorReporter);
+      verifier.generateUnusedImportHints(errorReporter);
+      verifier.generateUnusedShownNameHints(errorReporter);
+      verifier.generateUnnecessaryImportHints(
+          errorReporter, usedImportedElements);
+    }
+
+    // Unused local elements.
+    {
+      UnusedLocalElementsVerifier visitor = UnusedLocalElementsVerifier(
+          errorListener, usedElements, _inheritance, _libraryElement);
+      unit.accept(visitor);
+    }
+
+    //
+    // Find code that uses features from an SDK version that does not satisfy
+    // the SDK constraints specified in analysis options.
+    //
+    var sdkVersionConstraint = _analysisOptions.sdkVersionConstraint;
+    if (sdkVersionConstraint != null) {
+      SdkConstraintVerifier verifier = SdkConstraintVerifier(
+          errorReporter, _libraryElement, _typeProvider, sdkVersionConstraint);
+      unit.accept(verifier);
+    }
+  }
+
   /// Return a subset of the given [errors] that are not marked as ignored in
   /// the [file].
   List<AnalysisError> _filterIgnoredErrors(
diff --git a/analyzer/lib/src/dart/analysis/search.dart b/analyzer/lib/src/dart/analysis/search.dart
index 80f9029..1365e1b 100644
--- a/analyzer/lib/src/dart/analysis/search.dart
+++ b/analyzer/lib/src/dart/analysis/search.dart
@@ -738,6 +738,11 @@
     PatternVariableElementImpl element,
     SearchedFiles searchedFiles,
   ) async {
+    String path = element.source.fullName;
+    if (!searchedFiles.add(path, this)) {
+      return const <SearchResult>[];
+    }
+
     var rootVariable = element.rootVariable;
     var transitiveVariables = rootVariable is JoinPatternVariableElementImpl
         ? rootVariable.transitiveVariables
diff --git a/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart b/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart
index c6eb2ca..24c30f1 100644
--- a/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart
+++ b/analyzer/lib/src/dart/analysis/unlinked_unit_store.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/src/dart/analysis/unlinked_data.dart';
+import 'package:meta/meta.dart';
 
 abstract class UnlinkedUnitStore {
   void clear();
@@ -14,16 +15,17 @@
 class UnlinkedUnitStoreImpl implements UnlinkedUnitStore {
   // TODO(jensj): Could we use finalizers and automatically clean up
   // this map?
-  final Map<String, _UnlinkedUnitStoreData> _deserializedUnlinked = {};
+  @visibleForTesting
+  final Map<String, _UnlinkedUnitStoreData> map = {};
 
   @override
   void clear() {
-    _deserializedUnlinked.clear();
+    map.clear();
   }
 
   @override
   AnalysisDriverUnlinkedUnit? get(String key) {
-    var lookup = _deserializedUnlinked[key];
+    var lookup = map[key];
     if (lookup != null) {
       lookup.usageCount++;
       return lookup.value.target;
@@ -33,16 +35,16 @@
 
   @override
   void put(String key, AnalysisDriverUnlinkedUnit value) {
-    _deserializedUnlinked[key] = _UnlinkedUnitStoreData(WeakReference(value));
+    map[key] = _UnlinkedUnitStoreData(WeakReference(value));
   }
 
   @override
   void release(String key) {
-    var lookup = _deserializedUnlinked[key];
+    var lookup = map[key];
     if (lookup != null) {
       lookup.usageCount--;
       if (lookup.usageCount <= 0) {
-        _deserializedUnlinked.remove(key);
+        map.remove(key);
       }
     }
   }
diff --git a/analyzer/lib/src/dart/ast/ast.dart b/analyzer/lib/src/dart/ast/ast.dart
index 954d50e..5cae50f 100644
--- a/analyzer/lib/src/dart/ast/ast.dart
+++ b/analyzer/lib/src/dart/ast/ast.dart
@@ -1848,6 +1848,7 @@
   final Token? macroKeyword;
 
   /// The 'inline' keyword, or `null` if the keyword was absent.
+  @override
   final Token? inlineKeyword;
 
   /// The 'sealed' keyword, or `null` if the keyword was absent.
@@ -2859,7 +2860,7 @@
   }
 
   @override
-  Token get beginToken => expression.beginToken;
+  Token get beginToken => constKeyword ?? expression.beginToken;
 
   @override
   Token get endToken => expression.endToken;
@@ -3439,9 +3440,10 @@
 ///      | [LogicalAndPattern]
 ///      | [LogicalOrPattern]
 ///      | [MapPattern]
+///      | [NullAssertPattern]
+///      | [NullCheckPattern]
 ///      | [ObjectPattern]
 ///      | [ParenthesizedPattern]
-///      | [PostfixPattern]
 ///      | [RecordPattern]
 ///      | [RelationalPattern]
 @experimental
@@ -3662,12 +3664,10 @@
     ResolverVisitor resolverVisitor,
     SharedMatchContext context,
   ) {
-    declaredElement!.type = resolverVisitor.analyzeDeclaredVariablePattern(
-        context,
-        this,
-        declaredElement!,
-        declaredElement!.name,
-        type?.typeOrThrow);
+    declaredElement!.type = resolverVisitor
+        .analyzeDeclaredVariablePattern(context, this, declaredElement!,
+            declaredElement!.name, type?.typeOrThrow)
+        .staticType;
   }
 
   @override
@@ -4482,7 +4482,7 @@
       if (parent is ConstantContextForExpressionImpl) {
         return true;
       } else if (parent is ConstantPatternImpl) {
-        return true;
+        return parent.constKeyword != null;
       } else if (parent is EnumConstantArguments) {
         return true;
       } else if (parent is TypedLiteralImpl && parent.constKeyword != null) {
@@ -5847,6 +5847,7 @@
   @override
   Token? get star => null;
 
+  @Deprecated('Not used by clients')
   @override
   bool isPotentiallyMutatedInClosure(VariableElement variable) {
     if (localVariableInfo == null) {
@@ -8390,6 +8391,7 @@
   /// The set of local variables and parameters that are potentially mutated
   /// within a local function other than the function in which they are
   /// declared.
+  @Deprecated('Not used by clients')
   final Set<VariableElement> potentiallyMutatedInClosure = <VariableElement>{};
 
   /// The set of local variables and parameters that are potentially mutated
@@ -10456,6 +10458,16 @@
   Token get beginToken => name?.beginToken ?? pattern.beginToken;
 
   @override
+  String? get effectiveName {
+    final nameNode = name;
+    if (nameNode != null) {
+      final nameToken = nameNode.name ?? pattern.variablePattern?.name;
+      return nameToken?.lexeme;
+    }
+    return null;
+  }
+
+  @override
   Token get endToken => pattern.endToken;
 
   @override
@@ -12911,10 +12923,12 @@
 
   @override
   void resolveExpression(ResolverVisitor resolver, DartType contextType) {
+    var previousExhaustiveness = resolver.legacySwitchExhaustiveness;
     staticType = resolver
         .analyzeSwitchExpression(this, expression, cases.length, contextType)
         .type;
     resolver.popRewrite();
+    resolver.legacySwitchExhaustiveness = previousExhaustiveness;
   }
 
   @override
diff --git a/analyzer/lib/src/dart/ast/extensions.dart b/analyzer/lib/src/dart/ast/extensions.dart
index 997ccdb..77b9ab9 100644
--- a/analyzer/lib/src/dart/ast/extensions.dart
+++ b/analyzer/lib/src/dart/ast/extensions.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/syntactic_entity.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
@@ -165,6 +166,15 @@
     return _readElement(this);
   }
 
+  SimpleIdentifier get simpleName {
+    final self = this;
+    if (self is SimpleIdentifier) {
+      return self;
+    } else {
+      return (self as PrefixedIdentifier).identifier;
+    }
+  }
+
   Element? get writeElement {
     return _writeElement(this);
   }
@@ -191,6 +201,25 @@
   }
 }
 
+extension PatternFieldImplExtension on PatternFieldImpl {
+  /// A [SyntacticEntity] which can be used in error reporting, which is valid
+  /// for both explicit getter names (like `Rect(width: var w, height: var h)`)
+  /// and implicit getter names (like `Rect(:var width, :var height)`).
+  SyntacticEntity get errorEntity {
+    var fieldName = name;
+    if (fieldName == null) {
+      return this;
+    }
+    var fieldNameName = fieldName.name;
+    if (fieldNameName == null) {
+      var variablePattern = pattern.variablePattern;
+      return variablePattern?.name ?? this;
+    } else {
+      return fieldNameName;
+    }
+  }
+}
+
 extension RecordTypeAnnotationExtension on RecordTypeAnnotation {
   List<RecordTypeAnnotationField> get fields {
     return [
diff --git a/analyzer/lib/src/dart/constant/constant_verifier.dart b/analyzer/lib/src/dart/constant/constant_verifier.dart
index 567a756..fc4ba4e 100644
--- a/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -23,6 +23,7 @@
 import 'package:analyzer/src/dart/constant/potentially_constant.dart';
 import 'package:analyzer/src/dart/constant/value.dart';
 import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/least_greatest_closure.dart';
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
 import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
@@ -63,7 +64,9 @@
   /// the exhaustiveness of the switch has been checked.
   Map<ConstantPattern, DartObjectImpl>? _constantPatternValues;
 
-  final ExhaustivenessDataForTesting? exhaustivenessDataForTesting;
+  Map<Expression, DartObjectImpl>? _mapPatternKeyValues;
+
+  late final ExhaustivenessDataForTesting? exhaustivenessDataForTesting;
 
   /// Initialize a newly created constant verifier.
   ConstantVerifier(ErrorReporter errorReporter,
@@ -91,9 +94,11 @@
               _currentLibrary.featureSet.isEnabled(Feature.non_nullable),
           configuration: ConstantEvaluationConfiguration(),
         ),
-        _exhaustivenessCache = AnalyzerExhaustivenessCache(_typeSystem),
-        exhaustivenessDataForTesting =
-            retainDataForTesting ? ExhaustivenessDataForTesting() : null;
+        _exhaustivenessCache = AnalyzerExhaustivenessCache(_typeSystem) {
+    exhaustivenessDataForTesting = retainDataForTesting
+        ? ExhaustivenessDataForTesting(_exhaustivenessCache)
+        : null;
+  }
 
   @override
   void visitAnnotation(Annotation node) {
@@ -297,6 +302,7 @@
           CompileTimeErrorCode.NON_CONSTANT_MAP_PATTERN_KEY,
         );
         if (keyValue != null) {
+          _mapPatternKeyValues?[key] = keyValue;
           var existingKey = uniqueKeys[keyValue];
           if (existingKey != null) {
             duplicateKeys[key] = existingKey;
@@ -387,21 +393,23 @@
 
   @override
   void visitSwitchExpression(SwitchExpression node) {
-    _withConstantPatternValues((constantPatternValues) {
+    _withConstantPatternValues((mapPatternKeyValues, constantPatternValues) {
       super.visitSwitchExpression(node);
       _validateSwitchExhaustiveness(
         node: node,
         switchKeyword: node.switchKeyword,
         scrutinee: node.expression,
         caseNodes: node.cases,
+        mapPatternKeyValues: mapPatternKeyValues,
         constantPatternValues: constantPatternValues,
+        mustBeExhaustive: true,
       );
     });
   }
 
   @override
   void visitSwitchStatement(SwitchStatement node) {
-    _withConstantPatternValues((constantPatternValues) {
+    _withConstantPatternValues((mapPatternKeyValues, constantPatternValues) {
       super.visitSwitchStatement(node);
       if (_currentLibrary.featureSet.isEnabled(Feature.patterns)) {
         _validateSwitchExhaustiveness(
@@ -409,7 +417,10 @@
           switchKeyword: node.switchKeyword,
           scrutinee: node.expression,
           caseNodes: node.members,
+          mapPatternKeyValues: mapPatternKeyValues,
           constantPatternValues: constantPatternValues,
+          mustBeExhaustive:
+              _typeSystem.isAlwaysExhaustive(node.expression.typeOrThrow),
         );
       } else if (_currentLibrary.isNonNullableByDefault) {
         _validateSwitchStatement_nullSafety(node);
@@ -447,15 +458,23 @@
   /// `false`, taking into account the fact that [constantType] has primitive
   /// equality.
   bool _canBeEqual(DartType constantType, DartType valueType) {
-    if (constantType is InterfaceType && constantType.typeArguments.isEmpty) {
+    if (constantType is InterfaceType) {
       if (valueType is InterfaceType) {
-        return valueType.typeArguments.isEmpty &&
-            _typeSystem.isSubtypeOf(constantType, valueType);
+        if (constantType.isDartCoreInt && valueType.isDartCoreDouble) {
+          return true;
+        }
+        final valueTypeGreatest = PatternGreatestClosureHelper(
+          topType: _typeSystem.objectQuestion,
+          bottomType: NeverTypeImpl.instance,
+        ).eliminateToGreatest(valueType);
+        return _typeSystem.isSubtypeOf(constantType, valueTypeGreatest);
       } else if (valueType is TypeParameterTypeImpl) {
         final bound = valueType.promotedBound ?? valueType.element.bound;
         if (bound != null && !hasTypeParameterReference(bound)) {
           return _canBeEqual(constantType, bound);
         }
+      } else if (valueType is FunctionType) {
+        return false;
       }
     }
     // All other cases are not supported, so no warning.
@@ -746,7 +765,9 @@
     required Token switchKeyword,
     required Expression scrutinee,
     required List<AstNode> caseNodes,
+    required Map<Expression, DartObjectImpl> mapPatternKeyValues,
     required Map<ConstantPattern, DartObjectImpl> constantPatternValues,
+    required bool mustBeExhaustive,
   }) {
     final scrutineeType = scrutinee.typeOrThrow;
     final scrutineeTypeEx = _exhaustivenessCache.getStaticType(scrutineeType);
@@ -756,6 +777,11 @@
     var hasDefault = false;
 
     // Build spaces for cases.
+    final patternConverter = PatternConverter(
+      cache: _exhaustivenessCache,
+      mapPatternKeyValues: mapPatternKeyValues,
+      constantPatternValues: constantPatternValues,
+    );
     for (final caseNode in caseNodes) {
       GuardedPattern? guardedPattern;
       if (caseNode is SwitchDefault) {
@@ -769,29 +795,21 @@
       }
 
       if (guardedPattern != null) {
-        Space space;
-        if (guardedPattern.whenClause != null) {
-          // TODO(johnniwinther): Test this.
-          space = Space(_exhaustivenessCache.getUnknownStaticType());
-        } else {
-          final pattern = guardedPattern.pattern;
-          space = convertPatternToSpace(
-              _exhaustivenessCache, pattern, constantPatternValues);
-        }
+        Space space = patternConverter.createRootSpace(
+            scrutineeTypeEx, guardedPattern.pattern,
+            hasGuard: guardedPattern.whenClause != null);
         caseNodesWithSpace.add(caseNode);
         caseSpaces.add(space);
       }
     }
 
     // Prepare for recording data for testing.
-    List<Space>? remainingSpaces;
     final exhaustivenessDataForTesting = this.exhaustivenessDataForTesting;
-    if (exhaustivenessDataForTesting != null) {
-      remainingSpaces = [];
-    }
 
     // Compute and report errors.
-    final errors = reportErrors(scrutineeTypeEx, caseSpaces, remainingSpaces);
+    final errors =
+        reportErrors(_exhaustivenessCache, scrutineeTypeEx, caseSpaces);
+    final reportNonExhaustive = mustBeExhaustive && !hasDefault;
     for (final error in errors) {
       if (error is UnreachableCaseError) {
         final caseNode = caseNodesWithSpace[error.index];
@@ -807,39 +825,28 @@
           HintCode.UNREACHABLE_SWITCH_CASE,
           errorToken,
         );
-      } else if (error is NonExhaustiveError &&
-          _typeSystem.isAlwaysExhaustive(scrutineeType) &&
-          !hasDefault) {
+      } else if (error is NonExhaustiveError && reportNonExhaustive) {
         _errorReporter.reportErrorForToken(
           CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH,
           switchKeyword,
-          [scrutineeType, error.witness],
+          [scrutineeType, error.witness.toString()],
         );
       }
     }
 
     // Record data for testing.
-    if (exhaustivenessDataForTesting != null && remainingSpaces != null) {
-      assert(remainingSpaces.isEmpty ||
-          remainingSpaces.length == caseSpaces.length + 1);
+    if (exhaustivenessDataForTesting != null) {
       for (var i = 0; i < caseSpaces.length; i++) {
         final caseNode = caseNodesWithSpace[i];
         exhaustivenessDataForTesting.caseSpaces[caseNode] = caseSpaces[i];
-        if (remainingSpaces.isNotEmpty) {
-          exhaustivenessDataForTesting.remainingSpaces[caseNode] =
-              remainingSpaces[i];
-        }
       }
       exhaustivenessDataForTesting.switchScrutineeType[node] = scrutineeTypeEx;
-      if (remainingSpaces.isNotEmpty) {
-        exhaustivenessDataForTesting.remainingSpaces[node] =
-            remainingSpaces.last;
-      }
+      exhaustivenessDataForTesting.switchCases[node] = caseSpaces;
       for (var error in errors) {
         if (error is UnreachableCaseError) {
           exhaustivenessDataForTesting.errors[caseNodesWithSpace[error.index]] =
               error;
-        } else {
+        } else if (reportNonExhaustive) {
           exhaustivenessDataForTesting.errors[node] = error;
         }
       }
@@ -942,12 +949,17 @@
 
   /// Runs [f] with new [_constantPatternValues].
   void _withConstantPatternValues(
-    void Function(Map<ConstantPattern, DartObjectImpl> constantPatternValues) f,
+    void Function(Map<Expression, DartObjectImpl> mapPatternKeyValues,
+            Map<ConstantPattern, DartObjectImpl> constantPatternValues)
+        f,
   ) {
-    final previous = _constantPatternValues;
-    final values = _constantPatternValues = {};
-    f(values);
-    _constantPatternValues = previous;
+    final previousMapKeyValues = _mapPatternKeyValues;
+    final previousConstantPatternValues = _constantPatternValues;
+    final mapKeyValues = _mapPatternKeyValues = {};
+    final constantValues = _constantPatternValues = {};
+    f(mapKeyValues, constantValues);
+    _mapPatternKeyValues = previousMapKeyValues;
+    _constantPatternValues = previousConstantPatternValues;
   }
 }
 
diff --git a/analyzer/lib/src/dart/constant/evaluation.dart b/analyzer/lib/src/dart/constant/evaluation.dart
index da7f44d..541081e 100644
--- a/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/analyzer/lib/src/dart/constant/evaluation.dart
@@ -32,21 +32,29 @@
 import 'package:analyzer/src/utilities/extensions/collection.dart';
 
 class ConstantEvaluationConfiguration {
-  /// During evaluation of enum constants we might need to report an error
-  /// that is associated with the [InstanceCreationExpression], but this
-  /// expression is synthetic. Instead, we remember the corresponding
-  /// [EnumConstantDeclaration] and report the error on it.
-  final Map<Expression, EnumConstantDeclaration> _enumConstants = {};
+  final Map<AstNode, AstNode> _errorNodes = {};
 
-  void addEnumConstant({
-    required EnumConstantDeclaration declaration,
-    required Expression initializer,
+  /// We evaluate constant values using expressions stored in elements.
+  /// But these expressions don't have offsets set.
+  /// This includes elements and expressions of the file being resolved.
+  /// So, to make sure that we report errors at right offsets, we "replace"
+  /// these constant expressions.
+  ///
+  /// A similar issue happens for enum values, which are desugared into
+  /// synthetic [InstanceCreationExpression], which never had any offsets.
+  /// So, we remember that any errors should be reported at the corresponding
+  /// [EnumConstantDeclaration]s.
+  void addErrorNode({
+    required AstNode? fromElement,
+    required AstNode? fromAst,
   }) {
-    _enumConstants[initializer] = declaration;
+    if (fromElement != null && fromAst != null) {
+      _errorNodes[fromElement] = fromAst;
+    }
   }
 
   AstNode errorNode(AstNode node) {
-    return _enumConstants[node] ?? node;
+    return _errorNodes[node] ?? node;
   }
 }
 
@@ -544,6 +552,7 @@
         _substitution = substitution {
     _dartObjectComputer = DartObjectComputer(
       typeSystem,
+      _library.featureSet,
       _errorReporter,
     );
   }
@@ -613,7 +622,7 @@
     } else if (operatorType == TokenType.CARET) {
       return _dartObjectComputer.eagerXor(node, leftResult, rightResult);
     } else if (operatorType == TokenType.EQ_EQ) {
-      return _dartObjectComputer.lazyEqualEqual(node, leftResult, rightResult);
+      return _dartObjectComputer.equalEqual(node, leftResult, rightResult);
     } else if (operatorType == TokenType.GT) {
       return _dartObjectComputer.greaterThan(node, leftResult, rightResult);
     } else if (operatorType == TokenType.GT_EQ) {
@@ -927,6 +936,8 @@
 
     if (!_isNonNullableByDefault && hasTypeParameterReference(type)) {
       return super.visitNamedType(node);
+    } else {
+      node.name.accept(this);
     }
 
     if (_substitution != null) {
@@ -1417,7 +1428,8 @@
 
     // TODO(https://github.com/dart-lang/sdk/issues/47061): Use a specific
     // error code.
-    _error(node, null);
+    final errorNode = evaluationEngine.configuration.errorNode(node);
+    _error(errorNode, null);
     return null;
   }
 
@@ -1566,11 +1578,12 @@
 /// class and for collecting errors during evaluation.
 class DartObjectComputer {
   final TypeSystemImpl _typeSystem;
+  final FeatureSet _featureSet;
 
   /// The error reporter that we are using to collect errors.
   final ErrorReporter _errorReporter;
 
-  DartObjectComputer(this._typeSystem, this._errorReporter);
+  DartObjectComputer(this._typeSystem, this._featureSet, this._errorReporter);
 
   DartObjectImpl? add(BinaryExpression node, DartObjectImpl? leftOperand,
       DartObjectImpl? rightOperand) {
@@ -1698,7 +1711,7 @@
       DartObjectImpl? rightOperand) {
     if (leftOperand != null && rightOperand != null) {
       try {
-        return leftOperand.equalEqual(_typeSystem, rightOperand);
+        return leftOperand.equalEqual(_typeSystem, _featureSet, rightOperand);
       } on EvaluationException catch (exception) {
         _errorReporter.reportErrorForNode(exception.errorCode, node);
       }
@@ -1766,18 +1779,6 @@
     return null;
   }
 
-  DartObjectImpl? lazyEqualEqual(Expression node, DartObjectImpl? leftOperand,
-      DartObjectImpl? rightOperand) {
-    if (leftOperand != null && rightOperand != null) {
-      try {
-        return leftOperand.lazyEqualEqual(_typeSystem, rightOperand);
-      } on EvaluationException catch (exception) {
-        _errorReporter.reportErrorForNode(exception.errorCode, node);
-      }
-    }
-    return null;
-  }
-
   DartObjectImpl? lazyOr(BinaryExpression node, DartObjectImpl? leftOperand,
       DartObjectImpl? Function() rightOperandComputer) {
     if (leftOperand != null) {
@@ -1878,7 +1879,7 @@
       DartObjectImpl? rightOperand) {
     if (leftOperand != null && rightOperand != null) {
       try {
-        return leftOperand.notEqual(_typeSystem, rightOperand);
+        return leftOperand.notEqual(_typeSystem, _featureSet, rightOperand);
       } on EvaluationException catch (exception) {
         _errorReporter.reportErrorForNode(exception.errorCode, node);
       }
diff --git a/analyzer/lib/src/dart/constant/utilities.dart b/analyzer/lib/src/dart/constant/utilities.dart
index 94c5bf1..a2be807 100644
--- a/analyzer/lib/src/dart/constant/utilities.dart
+++ b/analyzer/lib/src/dart/constant/utilities.dart
@@ -159,10 +159,9 @@
     var element = node.declaredElement as ConstFieldElementImpl;
     constantsToCompute.add(element);
 
-    final initializer = element.constantInitializer!;
-    configuration.addEnumConstant(
-      declaration: node,
-      initializer: initializer,
+    configuration.addErrorNode(
+      fromElement: element.constantInitializer,
+      fromAst: node,
     );
   }
 
@@ -178,6 +177,14 @@
                 node.isFinal &&
                 !element.isStatic)) {
       constantsToCompute.add(element);
+      // Fill error nodes.
+      if (element is ConstVariableElement) {
+        final constElement = element as ConstVariableElement;
+        configuration.addErrorNode(
+          fromElement: constElement.constantInitializer,
+          fromAst: node.initializer,
+        );
+      }
     }
   }
 }
diff --git a/analyzer/lib/src/dart/constant/value.dart b/analyzer/lib/src/dart/constant/value.dart
index 30f9d0d..658e609 100644
--- a/analyzer/lib/src/dart/constant/value.dart
+++ b/analyzer/lib/src/dart/constant/value.dart
@@ -66,7 +66,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -430,7 +429,10 @@
   /// Throws an [EvaluationException] if the operator is not appropriate for an
   /// object of this kind.
   DartObjectImpl equalEqual(
-      TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
+    TypeSystemImpl typeSystem,
+    FeatureSet featureSet,
+    DartObjectImpl rightOperand,
+  ) {
     if (isNull || rightOperand.isNull) {
       return DartObjectImpl(
         typeSystem,
@@ -440,12 +442,22 @@
             : BoolState.FALSE_STATE,
       );
     }
-    if (isBoolNumStringOrNull) {
-      return DartObjectImpl(
-        typeSystem,
-        typeSystem.typeProvider.boolType,
-        state.equalEqual(typeSystem, rightOperand.state),
-      );
+    if (featureSet.isEnabled(Feature.patterns)) {
+      if (state is DoubleState || hasPrimitiveEquality(featureSet)) {
+        return DartObjectImpl(
+          typeSystem,
+          typeSystem.typeProvider.boolType,
+          state.equalEqual(typeSystem, rightOperand.state),
+        );
+      }
+    } else {
+      if (isBoolNumStringOrNull) {
+        return DartObjectImpl(
+          typeSystem,
+          typeSystem.typeProvider.boolType,
+          state.equalEqual(typeSystem, rightOperand.state),
+        );
+      }
     }
     throw EvaluationException(
         CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
@@ -593,33 +605,6 @@
     );
   }
 
-  /// Return the result of invoking the '==' operator on this object with the
-  /// [rightOperand].
-  ///
-  /// Throws an [EvaluationException] if the operator is not appropriate for an
-  /// object of this kind.
-  DartObjectImpl lazyEqualEqual(
-      TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
-    if (isNull || rightOperand.isNull) {
-      return DartObjectImpl(
-        typeSystem,
-        typeSystem.typeProvider.boolType,
-        isNull && rightOperand.isNull
-            ? BoolState.TRUE_STATE
-            : BoolState.FALSE_STATE,
-      );
-    }
-    if (isBoolNumStringOrNull) {
-      return DartObjectImpl(
-        typeSystem,
-        typeSystem.typeProvider.boolType,
-        state.lazyEqualEqual(typeSystem, rightOperand.state),
-      );
-    }
-    throw EvaluationException(
-        CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
-  }
-
   /// Return the result of invoking the '||' operator on this object with the
   /// [rightOperand].
   ///
@@ -740,8 +725,12 @@
   /// Throws an [EvaluationException] if the operator is not appropriate for an
   /// object of this kind.
   DartObjectImpl notEqual(
-      TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
-    return equalEqual(typeSystem, rightOperand).logicalNot(typeSystem);
+    TypeSystemImpl typeSystem,
+    FeatureSet featureSet,
+    DartObjectImpl rightOperand,
+  ) {
+    return equalEqual(typeSystem, featureSet, rightOperand)
+        .logicalNot(typeSystem);
   }
 
   /// Return the result of converting this object to a 'String'.
@@ -1471,7 +1460,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -1576,18 +1564,6 @@
     }
   }
 
-  /// Throw an exception if the given [state] does not represent a boolean,
-  /// numeric, string or null value.
-  void assertBoolNumStringOrNull(InstanceState state) {
-    if (!(state is BoolState ||
-        state is NumState ||
-        state is StringState ||
-        state is NullState)) {
-      throw EvaluationException(
-          CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
-    }
-  }
-
   /// Throw an exception if the given [state] does not represent an integer or
   /// null value.
   void assertIntOrNull(InstanceState state) {
@@ -1749,18 +1725,6 @@
     return rightOperand!.convertToBool();
   }
 
-  /// Return the result of invoking the '==' operator on this object with the
-  /// [rightOperand].
-  ///
-  /// Throws an [EvaluationException] if the operator is not appropriate for an
-  /// object of this kind.
-  BoolState lazyEqualEqual(
-    TypeSystemImpl typeSystem,
-    InstanceState rightOperand,
-  ) {
-    return isIdentical(typeSystem, rightOperand);
-  }
-
   /// Return the result of invoking the '||' operator on this object with the
   /// [rightOperand].
   ///
@@ -2416,7 +2380,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -2494,7 +2457,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -2556,7 +2518,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -2584,7 +2545,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 }
@@ -2761,7 +2721,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -2838,7 +2797,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -2900,7 +2858,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
@@ -2954,7 +2911,6 @@
 
   @override
   BoolState equalEqual(TypeSystemImpl typeSystem, InstanceState rightOperand) {
-    assertBoolNumStringOrNull(rightOperand);
     return isIdentical(typeSystem, rightOperand);
   }
 
diff --git a/analyzer/lib/src/dart/element/element.dart b/analyzer/lib/src/dart/element/element.dart
index c932e6a..3a93481 100644
--- a/analyzer/lib/src/dart/element/element.dart
+++ b/analyzer/lib/src/dart/element/element.dart
@@ -5,6 +5,8 @@
 import 'dart:collection';
 
 import 'package:_fe_analyzer_shared/src/scanner/string_canonicalizer.dart';
+import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
+    as shared;
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/ast/ast.dart';
@@ -28,6 +30,7 @@
 import 'package:analyzer/src/dart/element/member.dart';
 import 'package:analyzer/src/dart/element/nullability_eliminator.dart';
 import 'package:analyzer/src/dart/element/scope.dart';
+import 'package:analyzer/src/dart/element/since_sdk_version.dart';
 import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/type_algebra.dart';
 import 'package:analyzer/src/dart/element/type_provider.dart';
@@ -52,6 +55,7 @@
 import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer/src/utilities/extensions/string.dart';
 import 'package:collection/collection.dart';
+import 'package:pub_semver/pub_semver.dart';
 
 /// A concrete implementation of a [ClassElement].
 abstract class AbstractClassElementImpl extends _ExistingElementImpl
@@ -570,6 +574,10 @@
     implements BindPatternVariableElement {
   final DeclaredVariablePatternImpl node;
 
+  /// This flag is set to `true` if this variable clashes with another
+  /// pattern variable with the same name within the same pattern.
+  bool isDuplicate = false;
+
   BindPatternVariableElementImpl(this.node, super.name, super.offset);
 }
 
@@ -690,6 +698,9 @@
   }
 
   @override
+  bool get isConstructable => !isSealed && !isAbstract;
+
+  @override
   bool get isDartCoreEnum {
     return name == 'Enum' && library.isDartCore;
   }
@@ -840,7 +851,8 @@
   /// one (this is used to detect cycles).
   List<ConstructorElementImpl> _computeMixinAppConstructors(
       [List<ClassElementImpl>? visitedClasses]) {
-    if (supertype == null) {
+    final superType = supertype;
+    if (superType == null) {
       // Shouldn't ever happen, since the only classes with no supertype are
       // Object and mixins, and they aren't a mixin application. But for
       // safety's sake just assume an empty list.
@@ -848,7 +860,7 @@
       return <ConstructorElementImpl>[];
     }
 
-    var superElement = supertype!.element as ClassElementImpl;
+    final superElement = superType.element as ClassElementImpl;
 
     // First get the list of constructors of the superclass which need to be
     // forwarded to this class.
@@ -884,11 +896,11 @@
     var superClassParameters = superElement.typeParameters;
     List<DartType> argumentTypes = List<DartType>.filled(
         superClassParameters.length, DynamicTypeImpl.instance);
-    for (int i = 0; i < supertype!.typeArguments.length; i++) {
+    for (int i = 0; i < superType.typeArguments.length; i++) {
       if (i >= argumentTypes.length) {
         break;
       }
-      argumentTypes[i] = supertype!.typeArguments[i];
+      argumentTypes[i] = superType.typeArguments[i];
     }
     var substitution =
         Substitution.fromPairs(superClassParameters, argumentTypes);
@@ -958,7 +970,7 @@
       implicitConstructor.enclosingElement = this;
       // TODO(scheglov) Why do we manually map parameters types above?
       implicitConstructor.superConstructor =
-          ConstructorMember.from(superclassConstructor, supertype!);
+          ConstructorMember.from(superclassConstructor, superType);
 
       var isNamed = superclassConstructor.name.isNotEmpty;
       implicitConstructor.constantInitializers = [
@@ -2059,7 +2071,7 @@
 
   /// The AST of the annotation itself, cloned from the resolved AST for the
   /// source code.
-  late Annotation annotationAst;
+  late AnnotationImpl annotationAst;
 
   /// The result of evaluating this annotation as a compile-time constant
   /// expression, or `null` if the compilation unit containing the variable has
@@ -2083,6 +2095,15 @@
   @override
   bool get isConstantEvaluated => evaluationResult != null;
 
+  bool get isDartInternalSince {
+    final element = this.element;
+    if (element is ConstructorElement) {
+      return element.enclosingElement.name == 'Since' &&
+          element.library.source.uri.toString() == 'dart:_internal';
+    }
+    return false;
+  }
+
   @override
   bool get isDeprecated {
     final element = this.element;
@@ -2258,6 +2279,14 @@
   static const _metadataFlag_hasDeprecated = 1 << 1;
   static const _metadataFlag_hasOverride = 1 << 2;
 
+  /// Cached values for [sinceSdkVersion].
+  ///
+  /// Only very few elements have `@Since()` annotations, so instead of adding
+  /// an instance field to [ElementImpl], we attach this information this way.
+  /// We ask it only when [Modifier.HAS_SINCE_SDK_VERSION_VALUE] is `true`, so
+  /// don't pay for a hash lookup when we know that the result is `null`.
+  static final Expando<Version> _sinceSdkVersion = Expando<Version>();
+
   static int _NEXT_ID = 0;
 
   @override
@@ -2712,6 +2741,22 @@
   }
 
   @override
+  Version? get sinceSdkVersion {
+    if (!hasModifier(Modifier.HAS_SINCE_SDK_VERSION_COMPUTED)) {
+      setModifier(Modifier.HAS_SINCE_SDK_VERSION_COMPUTED, true);
+      final result = SinceSdkVersionComputer().compute(this);
+      if (result != null) {
+        _sinceSdkVersion[this] = result;
+        setModifier(Modifier.HAS_SINCE_SDK_VERSION_VALUE, true);
+      }
+    }
+    if (hasModifier(Modifier.HAS_SINCE_SDK_VERSION_VALUE)) {
+      return _sinceSdkVersion[this];
+    }
+    return null;
+  }
+
+  @override
   Source? get source {
     return enclosingElement?.source;
   }
@@ -3835,8 +3880,7 @@
   @override
   final List<PatternVariableElementImpl> variables;
 
-  @override
-  bool isConsistent;
+  shared.JoinedPatternVariableInconsistency inconsistency;
 
   /// The identifiers that reference this element.
   final List<SimpleIdentifier> references = [];
@@ -3845,7 +3889,7 @@
     super.name,
     super.offset,
     this.variables,
-    this.isConsistent,
+    this.inconsistency,
   ) {
     for (var component in variables) {
       component.join = this;
@@ -3855,6 +3899,11 @@
   @override
   int get hashCode => identityHashCode(this);
 
+  @override
+  bool get isConsistent {
+    return inconsistency == shared.JoinedPatternVariableInconsistency.none;
+  }
+
   /// Returns this variable, and variables that join into it.
   List<PatternVariableElementImpl> get transitiveVariables {
     var result = <PatternVariableElementImpl>[];
@@ -4617,6 +4666,7 @@
         definingCompilationUnit,
         ...libraryExports,
         ...libraryImports,
+        ...augmentationImports,
       ];
 
   @override
@@ -4976,58 +5026,66 @@
   static const Modifier HAS_PART_OF_DIRECTIVE =
       Modifier('HAS_PART_OF_DIRECTIVE', 15);
 
+  /// Indicates that the value of [Element.sinceSdkVersion] was computed.
+  static const Modifier HAS_SINCE_SDK_VERSION_COMPUTED =
+      Modifier('HAS_SINCE_SDK_VERSION_COMPUTED', 16);
+
+  /// [HAS_SINCE_SDK_VERSION_COMPUTED] and the value was not `null`.
+  static const Modifier HAS_SINCE_SDK_VERSION_VALUE =
+      Modifier('HAS_SINCE_SDK_VERSION_VALUE', 17);
+
   /// Indicates that the associated element did not have an explicit type
   /// associated with it. If the element is an [ExecutableElement], then the
   /// type being referred to is the return type.
-  static const Modifier IMPLICIT_TYPE = Modifier('IMPLICIT_TYPE', 16);
+  static const Modifier IMPLICIT_TYPE = Modifier('IMPLICIT_TYPE', 18);
 
   /// Indicates that the modifier 'interface' was applied to the element.
-  static const Modifier INTERFACE = Modifier('INTERFACE', 17);
+  static const Modifier INTERFACE = Modifier('INTERFACE', 19);
 
   /// Indicates that the method invokes the super method with the same name.
-  static const Modifier INVOKES_SUPER_SELF = Modifier('INVOKES_SUPER_SELF', 18);
+  static const Modifier INVOKES_SUPER_SELF = Modifier('INVOKES_SUPER_SELF', 20);
 
   /// Indicates that modifier 'lazy' was applied to the element.
-  static const Modifier LATE = Modifier('LATE', 19);
+  static const Modifier LATE = Modifier('LATE', 21);
 
   /// Indicates that a class is a macro builder.
-  static const Modifier MACRO = Modifier('MACRO', 20);
+  static const Modifier MACRO = Modifier('MACRO', 22);
 
   /// Indicates that a class is a mixin application.
-  static const Modifier MIXIN_APPLICATION = Modifier('MIXIN_APPLICATION', 21);
+  static const Modifier MIXIN_APPLICATION = Modifier('MIXIN_APPLICATION', 23);
 
   /// Indicates that a class is a mixin class.
-  static const Modifier MIXIN_CLASS = Modifier('MIXIN_CLASS', 22);
+  static const Modifier MIXIN_CLASS = Modifier('MIXIN_CLASS', 24);
 
-  static const Modifier PROMOTABLE = Modifier('IS_PROMOTABLE', 23);
+  static const Modifier PROMOTABLE = Modifier('IS_PROMOTABLE', 25);
 
   /// Indicates whether the type of a [PropertyInducingElementImpl] should be
   /// used to infer the initializer. We set it to `false` if the type was
   /// inferred from the initializer itself.
   static const Modifier SHOULD_USE_TYPE_FOR_INITIALIZER_INFERENCE =
-      Modifier('SHOULD_USE_TYPE_FOR_INITIALIZER_INFERENCE', 24);
+      Modifier('SHOULD_USE_TYPE_FOR_INITIALIZER_INFERENCE', 26);
 
   /// Indicates that the modifier 'sealed' was applied to the element.
-  static const Modifier SEALED = Modifier('SEALED', 25);
+  static const Modifier SEALED = Modifier('SEALED', 27);
 
   /// Indicates that the pseudo-modifier 'set' was applied to the element.
-  static const Modifier SETTER = Modifier('SETTER', 26);
+  static const Modifier SETTER = Modifier('SETTER', 28);
 
   /// See [TypeParameterizedElement.isSimplyBounded].
-  static const Modifier SIMPLY_BOUNDED = Modifier('SIMPLY_BOUNDED', 27);
+  static const Modifier SIMPLY_BOUNDED = Modifier('SIMPLY_BOUNDED', 29);
 
   /// Indicates that the modifier 'static' was applied to the element.
-  static const Modifier STATIC = Modifier('STATIC', 28);
+  static const Modifier STATIC = Modifier('STATIC', 30);
 
   /// Indicates that the element does not appear in the source code but was
   /// implicitly created. For example, if a class does not define any
   /// constructors, an implicit zero-argument constructor will be created and it
   /// will be marked as being synthetic.
-  static const Modifier SYNTHETIC = Modifier('SYNTHETIC', 29);
+  static const Modifier SYNTHETIC = Modifier('SYNTHETIC', 31);
 
   /// Indicates that the element was appended to this enclosing element to
   /// simulate temporary the effect of applying augmentation.
-  static const Modifier TEMP_AUGMENTATION = Modifier('TEMP_AUGMENTATION', 30);
+  static const Modifier TEMP_AUGMENTATION = Modifier('TEMP_AUGMENTATION', 32);
 
   static const List<Modifier> values = [
     ABSTRACT,
@@ -5046,6 +5104,8 @@
     GETTER,
     HAS_INITIALIZER,
     HAS_PART_OF_DIRECTIVE,
+    HAS_SINCE_SDK_VERSION_COMPUTED,
+    HAS_SINCE_SDK_VERSION_VALUE,
     IMPLICIT_TYPE,
     INTERFACE,
     INVOKES_SUPER_SELF,
@@ -5228,6 +5288,9 @@
   Element get nonSynthetic => this;
 
   @override
+  Version? get sinceSdkVersion => null;
+
+  @override
   Source? get source => null;
 
   @override
@@ -5877,6 +5940,9 @@
   DartType get returnTypeInternal => variable.type;
 
   @override
+  Version? get sinceSdkVersion => variable.sinceSdkVersion;
+
+  @override
   FunctionType get type => ElementTypeProvider.current.getExecutableType(this);
 
   @override
@@ -5947,6 +6013,9 @@
   DartType get returnTypeInternal => VoidTypeImpl.instance;
 
   @override
+  Version? get sinceSdkVersion => variable.sinceSdkVersion;
+
+  @override
   FunctionType get type => ElementTypeProvider.current.getExecutableType(this);
 
   @override
diff --git a/analyzer/lib/src/dart/element/least_greatest_closure.dart b/analyzer/lib/src/dart/element/least_greatest_closure.dart
index d96fd08..5fcc6e8 100644
--- a/analyzer/lib/src/dart/element/least_greatest_closure.dart
+++ b/analyzer/lib/src/dart/element/least_greatest_closure.dart
@@ -94,3 +94,36 @@
     return super.visitTypeParameterType(type);
   }
 }
+
+class PatternGreatestClosureHelper extends ReplacementVisitor {
+  final TypeImpl topType;
+  final TypeImpl bottomType;
+  bool _isCovariant = true;
+
+  PatternGreatestClosureHelper({
+    required this.topType,
+    required this.bottomType,
+  });
+
+  @override
+  void changeVariance() {
+    _isCovariant = !_isCovariant;
+  }
+
+  /// Returns a supertype of [type] for all values of type parameters.
+  DartType eliminateToGreatest(DartType type) {
+    _isCovariant = true;
+    return type.accept(this) ?? type;
+  }
+
+  @override
+  DartType? visitTypeParameterType(TypeParameterType type) {
+    final replacement = _isCovariant ? topType : bottomType;
+    return replacement.withNullability(
+      uniteNullabilities(
+        replacement.nullabilitySuffix,
+        type.nullabilitySuffix,
+      ),
+    );
+  }
+}
diff --git a/analyzer/lib/src/dart/element/member.dart b/analyzer/lib/src/dart/element/member.dart
index a537763..10997ba 100644
--- a/analyzer/lib/src/dart/element/member.dart
+++ b/analyzer/lib/src/dart/element/member.dart
@@ -15,6 +15,7 @@
 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/utilities_dart.dart';
+import 'package:pub_semver/pub_semver.dart';
 
 /// A constructor element defined in a parameterized type where the values of
 /// the type parameters are known.
@@ -632,6 +633,9 @@
   @override
   AnalysisSession? get session => _declaration.session;
 
+  @override
+  Version? get sinceSdkVersion => _declaration.sinceSdkVersion;
+
   /// The substitution for type parameters referenced in the base element.
   MapSubstitution get substitution => _substitution;
 
diff --git a/analyzer/lib/src/dart/element/since_sdk_version.dart b/analyzer/lib/src/dart/element/since_sdk_version.dart
new file mode 100644
index 0000000..d998ef1
--- /dev/null
+++ b/analyzer/lib/src/dart/element/since_sdk_version.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2023, 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:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:collection/collection.dart';
+import 'package:pub_semver/pub_semver.dart';
+
+class SinceSdkVersionComputer {
+  static final RegExp _asLanguageVersion = RegExp(r'^\d+\.\d+$');
+
+  /// The [element] is a `dart:xyz` library, so it can have `@Since` annotations.
+  /// Evaluates its annotations and returns the version.
+  Version? compute(ElementImpl element) {
+    // Must be in a `dart:` library.
+    final librarySource = element.librarySource;
+    if (librarySource == null || !librarySource.uri.isScheme('dart')) {
+      return null;
+    }
+
+    // Fields cannot be referenced outside.
+    if (element is FieldElementImpl && element.isSynthetic) {
+      return null;
+    }
+
+    // We cannot add required parameters.
+    if (element is ParameterElementImpl && element.isRequired) {
+      return null;
+    }
+
+    final specified = _specifiedVersion(element);
+    final enclosing = element.enclosingElement?.sinceSdkVersion;
+    return specified.maxWith(enclosing);
+  }
+
+  /// Returns the parsed [Version], or `null` if wrong format.
+  static Version? _parseVersion(String versionStr) {
+    // 2.15
+    if (_asLanguageVersion.hasMatch(versionStr)) {
+      return Version.parse('$versionStr.0');
+    }
+
+    // 2.19.3 or 3.0.0-dev.4
+    try {
+      return Version.parse(versionStr);
+    } on FormatException {
+      return null;
+    }
+  }
+
+  /// Returns the maximal specified `@Since()` version, `null` if none.
+  static Version? _specifiedVersion(ElementImpl element) {
+    Version? result;
+    for (final annotation in element.metadata) {
+      annotation as ElementAnnotationImpl;
+      if (annotation.isDartInternalSince) {
+        final arguments = annotation.annotationAst.arguments?.arguments;
+        final versionNode = arguments?.singleOrNull;
+        if (versionNode is SimpleStringLiteralImpl) {
+          final versionStr = versionNode.value;
+          final version = _parseVersion(versionStr);
+          if (version != null) {
+            result = result.maxWith(version);
+          }
+        }
+      }
+    }
+    return result;
+  }
+}
+
+extension on Version? {
+  Version? maxWith(Version? other) {
+    final self = this;
+    if (self == null) {
+      return other;
+    } else if (other == null) {
+      return self;
+    } else if (self >= other) {
+      return self;
+    } else {
+      return other;
+    }
+  }
+}
diff --git a/analyzer/lib/src/dart/element/type.dart b/analyzer/lib/src/dart/element/type.dart
index 671cf20..5dd7288 100644
--- a/analyzer/lib/src/dart/element/type.dart
+++ b/analyzer/lib/src/dart/element/type.dart
@@ -623,6 +623,11 @@
   }
 
   @override
+  bool get isDartCoreType {
+    return element.name == "Type" && element.library.isDartCore;
+  }
+
+  @override
   List<MethodElement> get methods {
     if (_methods == null) {
       List<MethodElement> methods = element.methods;
@@ -1233,6 +1238,9 @@
   bool get isDartCoreSymbol => false;
 
   @override
+  bool get isDartCoreType => false;
+
+  @override
   bool get isDynamic => false;
 
   @override
@@ -1462,6 +1470,7 @@
   @override
   int get hashCode => 2;
 
+  @Deprecated('Use `is VoidType` instead')
   @override
   bool get isVoid => true;
 
diff --git a/analyzer/lib/src/dart/element/type_system.dart b/analyzer/lib/src/dart/element/type_system.dart
index 36ac716..3adb6dc 100644
--- a/analyzer/lib/src/dart/element/type_system.dart
+++ b/analyzer/lib/src/dart/element/type_system.dart
@@ -33,6 +33,7 @@
 import 'package:analyzer/src/dart/element/type_schema_elimination.dart';
 import 'package:analyzer/src/dart/element/well_bounded.dart';
 import 'package:analyzer/src/utilities/extensions/collection.dart';
+import 'package:meta/meta.dart';
 
 /// Fresh type parameters created to unify two lists of type parameters.
 class RelatedTypeParameters {
@@ -237,36 +238,53 @@
   }
 
   @override
-  DartType flatten(DartType type) {
-    if (identical(type, UnknownInferredType.instance)) {
-      return type;
+  DartType flatten(DartType T) {
+    if (identical(T, UnknownInferredType.instance)) {
+      return T;
     }
 
     // if T is S? then flatten(T) = flatten(S)?
     // if T is S* then flatten(T) = flatten(S)*
-    NullabilitySuffix nullabilitySuffix = type.nullabilitySuffix;
+    final nullabilitySuffix = T.nullabilitySuffix;
     if (nullabilitySuffix != NullabilitySuffix.none) {
-      var S = (type as TypeImpl).withNullability(NullabilitySuffix.none);
+      final S = (T as TypeImpl).withNullability(NullabilitySuffix.none);
       return (flatten(S) as TypeImpl).withNullability(nullabilitySuffix);
     }
 
-    // otherwise if T is FutureOr<S> then flatten(T) = S
-    // otherwise if T is Future<S> then flatten(T) = S (shortcut)
-    if (type is InterfaceType) {
-      if (type.isDartAsyncFutureOr || type.isDartAsyncFuture) {
-        return type.typeArguments[0];
+    // If T is X & S for some type variable X and type S then:
+    if (T is TypeParameterTypeImpl) {
+      final S = T.promotedBound;
+      if (S != null) {
+        // * if S has future type U then flatten(T) = flatten(U)
+        final futureType = this.futureType(S);
+        if (futureType != null) {
+          return flatten(futureType);
+        }
+        // * otherwise, flatten(T) = flatten(X)
+        return flatten(
+          TypeParameterTypeImpl(
+            element: T.element,
+            nullabilitySuffix: nullabilitySuffix,
+          ),
+        );
       }
     }
 
-    // otherwise if T <: Future then let S be a type such that T <: Future<S>
-    //   and for all R, if T <: Future<R> then S <: R; then flatten(T) = S
-    var futureType = type.asInstanceOf(typeProvider.futureElement);
-    if (futureType != null) {
-      return futureType.typeArguments[0];
+    // If T has future type Future<S> or FutureOr<S> then flatten(T) = S
+    // If T has future type Future<S>? or FutureOr<S>? then flatten(T) = S?
+    final futureType = this.futureType(T);
+    if (futureType is InterfaceType) {
+      if (futureType.isDartAsyncFuture || futureType.isDartAsyncFutureOr) {
+        final S = futureType.typeArguments[0] as TypeImpl;
+        if (futureType.nullabilitySuffix == NullabilitySuffix.question) {
+          return S.withNullability(NullabilitySuffix.question);
+        }
+        return S;
+      }
     }
 
     // otherwise flatten(T) = T
-    return type;
+    return T;
   }
 
   DartType futureOrBase(DartType type) {
@@ -282,6 +300,23 @@
     return type;
   }
 
+  /// We say that S is the future type of a type T in the following cases,
+  /// using the first applicable case:
+  @visibleForTesting
+  DartType? futureType(DartType T) {
+    // T implements S, and there is a U such that S is Future<U>
+    if (T.nullabilitySuffix != NullabilitySuffix.question) {
+      final result = T.asInstanceOf(typeProvider.futureElement);
+      if (result != null) {
+        return result;
+      }
+    }
+
+    // T is S bounded, and there is a U such that S is FutureOr<U>,
+    // Future<U>?, or FutureOr<U>?.
+    return _futureTypeOfBounded(T);
+  }
+
   /// Compute "future value type" of [T].
   ///
   /// https://github.com/dart-lang/language/
@@ -759,10 +794,10 @@
 
     // If the subtype relation goes the other way, allow the implicit downcast.
     if (isSubtypeOf(toType, fromType)) {
-      // TODO(leafp,jmesserly): we emit warnings/hints for these in
-      // src/task/strong/checker.dart, which is a bit inconsistent. That
-      // code should be handled into places that use isAssignableTo, such as
-      // ErrorVerifier.
+      // TODO(leafp,jmesserly): we emit warnings for these in
+      // `src/task/strong/checker.dart`, which is a bit inconsistent. That code
+      // should be handled into places that use `isAssignableTo`, such as
+      // [ErrorVerifier].
       return true;
     }
 
@@ -1065,7 +1100,7 @@
 
   @override
   bool isNonNullable(DartType type) {
-    if (type.isDynamic || type.isVoid || type.isDartCoreNull) {
+    if (type.isDynamic || type is VoidType || type.isDartCoreNull) {
       return false;
     } else if (type is TypeParameterTypeImpl && type.promotedBound != null) {
       return isNonNullable(type.promotedBound!);
@@ -1106,7 +1141,7 @@
 
   @override
   bool isNullable(DartType type) {
-    if (type.isDynamic || type.isVoid || type.isDartCoreNull) {
+    if (type.isDynamic || type is VoidType || type.isDartCoreNull) {
       return true;
     } else if (type is TypeParameterTypeImpl && type.promotedBound != null) {
       return isNullable(type.promotedBound!);
@@ -1148,7 +1183,7 @@
 
   @override
   bool isStrictlyNonNullable(DartType type) {
-    if (type.isDynamic || type.isVoid || type.isDartCoreNull) {
+    if (type.isDynamic || type is VoidType || type.isDartCoreNull) {
       return false;
     } else if (type.nullabilitySuffix != NullabilitySuffix.none) {
       return false;
@@ -1688,6 +1723,50 @@
     }).toFixedList();
   }
 
+  /// `S` is the future type of a type `T` in the following cases, using the
+  /// first applicable case:
+  /// * see [futureType].
+  /// * `T` is `S` bounded, and there is a `U` such that `S` is `FutureOr<U>`,
+  /// `Future<U>?`, or `FutureOr<U>?`.
+  ///
+  /// 17.15.3: For a given type `T0`, we introduce the notion of a `T0` bounded
+  /// type: `T0` itself is `T0` bounded; if `B` is `T0` bounded and `X` is a
+  /// type variable with bound `B` then `X` is `T0` bounded; finally, if `B`
+  /// is `T0` bounded and `X` is a type variable then `X&B` is `T0` bounded.
+  DartType? _futureTypeOfBounded(DartType T) {
+    if (T is InterfaceType) {
+      if (T.nullabilitySuffix != NullabilitySuffix.question) {
+        if (T.isDartAsyncFutureOr) {
+          return T;
+        }
+      } else {
+        if (T.isDartAsyncFutureOr || T.isDartAsyncFuture) {
+          return T;
+        }
+      }
+    }
+
+    if (T is TypeParameterTypeImpl) {
+      final bound = T.element.bound;
+      if (bound != null) {
+        final result = _futureTypeOfBounded(bound);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      final promotedBound = T.promotedBound;
+      if (promotedBound != null) {
+        final result = _futureTypeOfBounded(promotedBound);
+        if (result != null) {
+          return result;
+        }
+      }
+    }
+
+    return null;
+  }
+
   DartType _refineBinaryExpressionTypeLegacy(DartType leftType,
       TokenType operator, DartType rightType, DartType currentType) {
     if (leftType is TypeParameterType && leftType.bound.isDartCoreNum) {
diff --git a/analyzer/lib/src/dart/error/hint_codes.g.dart b/analyzer/lib/src/dart/error/hint_codes.g.dart
index 9436803..d9438af 100644
--- a/analyzer/lib/src/dart/error/hint_codes.g.dart
+++ b/analyzer/lib/src/dart/error/hint_codes.g.dart
@@ -15,73 +15,23 @@
 import "package:analyzer/src/error/analyzer_error_code.dart";
 
 class HintCode extends AnalyzerErrorCode {
-  ///  Users should not assign values marked `@doNotStore`.
-  ///
-  ///  Parameters:
-  ///  0: the name of the field or variable
-  static const HintCode ASSIGNMENT_OF_DO_NOT_STORE = HintCode(
-    'ASSIGNMENT_OF_DO_NOT_STORE',
-    "'{0}' is marked 'doNotStore' and shouldn't be assigned to a field or "
-        "top-level variable.",
-    correctionMessage: "Try removing the assignment.",
-    hasPublishedDocs: true,
-  );
-
   ///  When the target expression uses '?.' operator, it can be `null`, so all the
   ///  subsequent invocations should also use '?.' operator.
   ///
   ///  Note: This diagnostic is only generated in pre-null safe code.
+  ///
+  ///  Note: Since this diagnostic is only produced in pre-null safe code, we do
+  ///  not plan to go through the exercise of converting it to a Warning.
   static const HintCode CAN_BE_NULL_AFTER_NULL_AWARE = HintCode(
     'CAN_BE_NULL_AFTER_NULL_AWARE',
     "The receiver uses '?.', so its value can be null.",
     correctionMessage: "Replace the '.' with a '?.' in the invocation.",
   );
 
-  ///  Dead code is code that is never reached, this can happen for instance if a
-  ///  statement follows a return statement.
-  ///
   ///  No parameters.
-  static const HintCode DEAD_CODE = HintCode(
-    'DEAD_CODE',
-    "Dead code.",
-    correctionMessage:
-        "Try removing the code, or fixing the code before it so that it can be "
-        "reached.",
-    hasPublishedDocs: true,
-  );
-
-  ///  Dead code is code that is never reached. This case covers cases where the
-  ///  user has catch clauses after `catch (e)` or `on Object catch (e)`.
   ///
-  ///  No parameters.
-  static const HintCode DEAD_CODE_CATCH_FOLLOWING_CATCH = HintCode(
-    'DEAD_CODE_CATCH_FOLLOWING_CATCH',
-    "Dead code: Catch clauses after a 'catch (e)' or an 'on Object catch (e)' "
-        "are never reached.",
-    correctionMessage:
-        "Try reordering the catch clauses so that they can be reached, or "
-        "removing the unreachable catch clauses.",
-    hasPublishedDocs: true,
-  );
-
-  ///  Dead code is code that is never reached. This case covers cases where the
-  ///  user has an on-catch clause such as `on A catch (e)`, where a supertype of
-  ///  `A` was already caught.
-  ///
-  ///  Parameters:
-  ///  0: name of the subtype
-  ///  1: name of the supertype
-  static const HintCode DEAD_CODE_ON_CATCH_SUBTYPE = HintCode(
-    'DEAD_CODE_ON_CATCH_SUBTYPE',
-    "Dead code: This on-catch block won't be executed because '{0}' is a "
-        "subtype of '{1}' and hence will have been caught already.",
-    correctionMessage:
-        "Try reordering the catch clauses so that this block can be reached, "
-        "or removing the unreachable catch clause.",
-    hasPublishedDocs: true,
-  );
-
-  ///  No parameters.
+  ///  Note: Since this diagnostic is only produced in pre-3.0 code, we do not
+  ///  plan to go through the exercise of converting it to a Warning.
   static const HintCode DEPRECATED_COLON_FOR_DEFAULT_VALUE = HintCode(
     'DEPRECATED_COLON_FOR_DEFAULT_VALUE',
     "Using a colon as a separator before a default value is deprecated and "
@@ -153,34 +103,6 @@
     hasPublishedDocs: true,
   );
 
-  ///  It is a bad practice for a source file in a package "lib" directory
-  ///  hierarchy to traverse outside that directory hierarchy. For example, a
-  ///  source file in the "lib" directory should not contain a directive such as
-  ///  `import '../web/some.dart'` which references a file outside the lib
-  ///  directory.
-  static const HintCode FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE =
-      HintCode(
-    'FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE',
-    "A file in the 'lib' directory shouldn't import a file outside the 'lib' "
-        "directory.",
-    correctionMessage:
-        "Try removing the import, or moving the imported file inside the 'lib' "
-        "directory.",
-  );
-
-  ///  It is a bad practice for a source file outside a package "lib" directory
-  ///  hierarchy to traverse into that directory hierarchy. For example, a source
-  ///  file in the "web" directory should not contain a directive such as
-  ///  `import '../lib/some.dart'` which references a file inside the lib
-  ///  directory.
-  static const HintCode FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE =
-      HintCode(
-    'FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE',
-    "A file outside the 'lib' directory shouldn't reference a file inside the "
-        "'lib' directory using a relative path.",
-    correctionMessage: "Try using a 'package:' URI instead.",
-  );
-
   ///  No parameters.
   static const HintCode IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION = HintCode(
     'IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION',
@@ -204,144 +126,6 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere where a `@sealed` class is used as a
-  ///  a superclass constraint of a mixin.
-  ///
-  ///  Parameters:
-  ///  0: the name of the sealed class
-  static const HintCode MIXIN_ON_SEALED_CLASS = HintCode(
-    'MIXIN_ON_SEALED_CLASS',
-    "The class '{0}' shouldn't be used as a mixin constraint because it is "
-        "sealed, and any class mixing in this mixin must have '{0}' as a "
-        "superclass.",
-    correctionMessage:
-        "Try composing with this class, or refer to its documentation for more "
-        "information.",
-    hasPublishedDocs: true,
-  );
-
-  ///  Generate a hint for non-const instance creation using a constructor
-  ///  annotated with `@literal`.
-  ///
-  ///  Parameters:
-  ///  0: the name of the class defining the annotated constructor
-  static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR = HintCode(
-    'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
-    "This instance creation must be 'const', because the {0} constructor is "
-        "marked as '@literal'.",
-    correctionMessage: "Try adding a 'const' keyword.",
-    hasPublishedDocs: true,
-  );
-
-  ///  Generate a hint for non-const instance creation (with the `new` keyword)
-  ///  using a constructor annotated with `@literal`.
-  ///
-  ///  Parameters:
-  ///  0: the name of the class defining the annotated constructor
-  static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW =
-      HintCode(
-    'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
-    "This instance creation must be 'const', because the {0} constructor is "
-        "marked as '@literal'.",
-    correctionMessage: "Try replacing the 'new' keyword with 'const'.",
-    hasPublishedDocs: true,
-    uniqueName: 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW',
-  );
-
-  ///  No parameters.
-  static const HintCode NULL_CHECK_ALWAYS_FAILS = HintCode(
-    'NULL_CHECK_ALWAYS_FAILS',
-    "This null-check will always throw an exception because the expression "
-        "will always evaluate to 'null'.",
-    hasPublishedDocs: true,
-  );
-
-  ///  A field with the override annotation does not override a getter or setter.
-  ///
-  ///  No parameters.
-  static const HintCode OVERRIDE_ON_NON_OVERRIDING_FIELD = HintCode(
-    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
-    "The field doesn't override an inherited getter or setter.",
-    correctionMessage:
-        "Try updating this class to match the superclass, or removing the "
-        "override annotation.",
-    hasPublishedDocs: true,
-    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_FIELD',
-  );
-
-  ///  A getter with the override annotation does not override an existing getter.
-  ///
-  ///  No parameters.
-  static const HintCode OVERRIDE_ON_NON_OVERRIDING_GETTER = HintCode(
-    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
-    "The getter doesn't override an inherited getter.",
-    correctionMessage:
-        "Try updating this class to match the superclass, or removing the "
-        "override annotation.",
-    hasPublishedDocs: true,
-    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_GETTER',
-  );
-
-  ///  A method with the override annotation does not override an existing method.
-  ///
-  ///  No parameters.
-  static const HintCode OVERRIDE_ON_NON_OVERRIDING_METHOD = HintCode(
-    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
-    "The method doesn't override an inherited method.",
-    correctionMessage:
-        "Try updating this class to match the superclass, or removing the "
-        "override annotation.",
-    hasPublishedDocs: true,
-    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_METHOD',
-  );
-
-  ///  A setter with the override annotation does not override an existing setter.
-  ///
-  ///  No parameters.
-  static const HintCode OVERRIDE_ON_NON_OVERRIDING_SETTER = HintCode(
-    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
-    "The setter doesn't override an inherited setter.",
-    correctionMessage:
-        "Try updating this class to match the superclass, or removing the "
-        "override annotation.",
-    hasPublishedDocs: true,
-    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_SETTER',
-  );
-
-  ///  When "strict-raw-types" is enabled, "raw types" must have type arguments.
-  ///
-  ///  A "raw type" is a type name that does not use inference to fill in missing
-  ///  type arguments; instead, each type argument is instantiated to its bound.
-  ///
-  ///  Parameters:
-  ///  0: the name of the generic type
-  static const HintCode STRICT_RAW_TYPE = HintCode(
-    'STRICT_RAW_TYPE',
-    "The generic type '{0}' should have explicit type arguments but doesn't.",
-    correctionMessage: "Use explicit type arguments for '{0}'.",
-  );
-
-  ///  Parameters:
-  ///  0: the name of the sealed class
-  static const HintCode SUBTYPE_OF_SEALED_CLASS = HintCode(
-    'SUBTYPE_OF_SEALED_CLASS',
-    "The class '{0}' shouldn't be extended, mixed in, or implemented because "
-        "it's sealed.",
-    correctionMessage:
-        "Try composing instead of inheriting, or refer to the documentation of "
-        "'{0}' for more information.",
-    hasPublishedDocs: true,
-  );
-
-  ///  Parameters:
-  ///  0: the name of the undefined parameter
-  ///  1: the name of the targeted member
-  static const HintCode UNDEFINED_REFERENCED_PARAMETER = HintCode(
-    'UNDEFINED_REFERENCED_PARAMETER',
-    "The parameter '{0}' isn't defined by '{1}'.",
-    hasPublishedDocs: true,
-  );
-
   ///  Parameters:
   ///  0: the name of the non-diagnostic being ignored
   static const HintCode UNIGNORABLE_IGNORE = HintCode(
@@ -392,88 +176,12 @@
   );
 
   ///  No parameters.
-  static const HintCode UNNECESSARY_NAN_COMPARISON_FALSE = HintCode(
-    'UNNECESSARY_NAN_COMPARISON',
-    "A double can't equal 'double.nan', so the condition is always 'false'.",
-    correctionMessage: "Try using 'double.isNan', or removing the condition.",
-    uniqueName: 'UNNECESSARY_NAN_COMPARISON_FALSE',
-  );
-
-  ///  No parameters.
-  static const HintCode UNNECESSARY_NAN_COMPARISON_TRUE = HintCode(
-    'UNNECESSARY_NAN_COMPARISON',
-    "A double can't equal 'double.nan', so the condition is always 'true'.",
-    correctionMessage: "Try using 'double.isNan', or removing the condition.",
-    uniqueName: 'UNNECESSARY_NAN_COMPARISON_TRUE',
-  );
-
-  ///  No parameters.
-  static const HintCode UNNECESSARY_NO_SUCH_METHOD = HintCode(
-    'UNNECESSARY_NO_SUCH_METHOD',
-    "Unnecessary 'noSuchMethod' declaration.",
-    correctionMessage: "Try removing the declaration of 'noSuchMethod'.",
-    hasPublishedDocs: true,
-  );
-
-  ///  No parameters.
-  static const HintCode UNNECESSARY_NULL_COMPARISON_FALSE = HintCode(
-    'UNNECESSARY_NULL_COMPARISON',
-    "The operand can't be null, so the condition is always 'false'.",
-    correctionMessage:
-        "Try removing the condition, an enclosing condition, or the whole "
-        "conditional statement.",
-    hasPublishedDocs: true,
-    uniqueName: 'UNNECESSARY_NULL_COMPARISON_FALSE',
-  );
-
-  ///  No parameters.
-  static const HintCode UNNECESSARY_NULL_COMPARISON_TRUE = HintCode(
-    'UNNECESSARY_NULL_COMPARISON',
-    "The operand can't be null, so the condition is always 'true'.",
-    correctionMessage: "Remove the condition.",
-    hasPublishedDocs: true,
-    uniqueName: 'UNNECESSARY_NULL_COMPARISON_TRUE',
-  );
-
-  ///  Parameters:
-  ///  0: the name of the type
-  static const HintCode UNNECESSARY_QUESTION_MARK = HintCode(
-    'UNNECESSARY_QUESTION_MARK',
-    "The '?' is unnecessary because '{0}' is nullable without it.",
-    hasPublishedDocs: true,
-  );
-
-  ///  No parameters.
-  static const HintCode UNNECESSARY_SET_LITERAL = HintCode(
-    'UNNECESSARY_SET_LITERAL',
-    "Braces unnecessarily wrap this expression in a set literal.",
-    correctionMessage: "Try removing the set literal around the expression.",
-  );
-
-  ///  No parameters.
-  static const HintCode UNNECESSARY_TYPE_CHECK_FALSE = HintCode(
-    'UNNECESSARY_TYPE_CHECK',
-    "Unnecessary type check; the result is always 'false'.",
-    correctionMessage:
-        "Try correcting the type check, or removing the type check.",
-    hasPublishedDocs: true,
-    uniqueName: 'UNNECESSARY_TYPE_CHECK_FALSE',
-  );
-
-  ///  No parameters.
-  static const HintCode UNNECESSARY_TYPE_CHECK_TRUE = HintCode(
-    'UNNECESSARY_TYPE_CHECK',
-    "Unnecessary type check; the result is always 'true'.",
-    correctionMessage:
-        "Try correcting the type check, or removing the type check.",
-    hasPublishedDocs: true,
-    uniqueName: 'UNNECESSARY_TYPE_CHECK_TRUE',
-  );
-
-  ///  No parameters.
   static const HintCode UNREACHABLE_SWITCH_CASE = HintCode(
     'UNREACHABLE_SWITCH_CASE',
     "This case is covered by the previous cases.",
+    correctionMessage:
+        "Try removing the case clause, or restructuring the preceding "
+        "patterns.",
   );
 
   ///  Parameters:
@@ -514,17 +222,6 @@
   );
 
   ///  Parameters:
-  ///  0: the label that isn't used
-  static const HintCode UNUSED_LABEL = HintCode(
-    'UNUSED_LABEL',
-    "The label '{0}' isn't used.",
-    correctionMessage:
-        "Try removing the label, or using it in either a 'break' or 'continue' "
-        "statement.",
-    hasPublishedDocs: true,
-  );
-
-  ///  Parameters:
   ///  0: the name of the unused variable
   static const HintCode UNUSED_LOCAL_VARIABLE = HintCode(
     'UNUSED_LOCAL_VARIABLE',
@@ -534,34 +231,6 @@
   );
 
   ///  Parameters:
-  ///  0: the name of the annotated method, property or function
-  static const HintCode UNUSED_RESULT = HintCode(
-    'UNUSED_RESULT',
-    "The value of '{0}' should be used.",
-    correctionMessage:
-        "Try using the result by invoking a member, passing it to a function, "
-        "or returning it from this function.",
-    hasPublishedDocs: true,
-  );
-
-  ///  The result of invoking a method, property, or function annotated with
-  ///  `@useResult` must be used (assigned, passed to a function as an argument,
-  ///  or returned by a function).
-  ///
-  ///  Parameters:
-  ///  0: the name of the annotated method, property or function
-  ///  1: message details
-  static const HintCode UNUSED_RESULT_WITH_MESSAGE = HintCode(
-    'UNUSED_RESULT',
-    "'{0}' should be used. {1}.",
-    correctionMessage:
-        "Try using the result by invoking a member, passing it to a function, "
-        "or returning it from this function.",
-    hasPublishedDocs: true,
-    uniqueName: 'UNUSED_RESULT_WITH_MESSAGE',
-  );
-
-  ///  Parameters:
   ///  0: the name that is shown but not used
   static const HintCode UNUSED_SHOWN_NAME = HintCode(
     'UNUSED_SHOWN_NAME',
diff --git a/analyzer/lib/src/dart/error/syntactic_errors.g.dart b/analyzer/lib/src/dart/error/syntactic_errors.g.dart
index 5528d70..9f8e58f 100644
--- a/analyzer/lib/src/dart/error/syntactic_errors.g.dart
+++ b/analyzer/lib/src/dart/error/syntactic_errors.g.dart
@@ -156,6 +156,9 @@
   ParserErrorCode.INVALID_CONSTANT_PATTERN_GENERIC,
   ParserErrorCode.INVALID_CONSTANT_CONST_PREFIX,
   ParserErrorCode.INVALID_CONSTANT_PATTERN_BINARY,
+  ParserErrorCode.FINAL_MIXIN_CLASS,
+  ParserErrorCode.INTERFACE_MIXIN_CLASS,
+  ParserErrorCode.SEALED_MIXIN_CLASS,
 ];
 
 class ParserErrorCode extends ErrorCode {
@@ -462,20 +465,20 @@
   static const ParserErrorCode EMPTY_RECORD_LITERAL_WITH_COMMA =
       ParserErrorCode(
     'EMPTY_RECORD_LITERAL_WITH_COMMA',
-    "Record literal without fields can't have a trailing comma.",
+    "A record literal without fields can't have a trailing comma.",
     correctionMessage: "Try removing the trailing comma.",
   );
 
   static const ParserErrorCode EMPTY_RECORD_TYPE_NAMED_FIELDS_LIST =
       ParserErrorCode(
     'EMPTY_RECORD_TYPE_NAMED_FIELDS_LIST',
-    "Record type named fields list can't be empty.",
-    correctionMessage: "Try adding a record type named field to the list.",
+    "The list of named fields in a record type can't be empty.",
+    correctionMessage: "Try adding a named field to the list.",
   );
 
   static const ParserErrorCode EMPTY_RECORD_TYPE_WITH_COMMA = ParserErrorCode(
     'EMPTY_RECORD_TYPE_WITH_COMMA',
-    "Record type without fields can't have a trailing comma.",
+    "A record type without fields can't have a trailing comma.",
     correctionMessage: "Try removing the trailing comma.",
   );
 
@@ -820,6 +823,12 @@
     correctionMessage: "Try removing the keyword 'final'.",
   );
 
+  static const ParserErrorCode FINAL_MIXIN_CLASS = ParserErrorCode(
+    'FINAL_MIXIN_CLASS',
+    "A mixin class can't be declared 'final'.",
+    correctionMessage: "Try removing the 'final' keyword.",
+  );
+
   static const ParserErrorCode FINAL_TYPEDEF = ParserErrorCode(
     'FINAL_TYPEDEF',
     "Typedefs can't be declared to be 'final'.",
@@ -897,6 +906,12 @@
         "Try removing the initializer, or using a different kind of loop.",
   );
 
+  static const ParserErrorCode INTERFACE_MIXIN_CLASS = ParserErrorCode(
+    'INTERFACE_MIXIN_CLASS',
+    "A mixin class can't be declared 'interface'.",
+    correctionMessage: "Try removing the 'interface' keyword.",
+  );
+
   static const ParserErrorCode INVALID_AWAIT_IN_FOR = ParserErrorCode(
     'INVALID_AWAIT_IN_FOR',
     "The keyword 'await' isn't allowed for a normal 'for' statement.",
@@ -1517,14 +1532,16 @@
   static const ParserErrorCode RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA =
       ParserErrorCode(
     'RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA',
-    "Record literal with one field requires a trailing comma.",
+    "A record literal with exactly one positional field requires a trailing "
+        "comma.",
     correctionMessage: "Try adding a trailing comma.",
   );
 
   static const ParserErrorCode RECORD_TYPE_ONE_POSITIONAL_NO_TRAILING_COMMA =
       ParserErrorCode(
     'RECORD_TYPE_ONE_POSITIONAL_NO_TRAILING_COMMA',
-    "Record type with one entry requires a trailing comma.",
+    "A record type with exactly one positional field requires a trailing "
+        "comma.",
     correctionMessage: "Try adding a trailing comma.",
   );
 
@@ -1544,6 +1561,12 @@
         "Try making this a factory constructor, or remove the redirection.",
   );
 
+  static const ParserErrorCode SEALED_MIXIN_CLASS = ParserErrorCode(
+    'SEALED_MIXIN_CLASS',
+    "A mixin class can't be declared 'sealed'.",
+    correctionMessage: "Try removing the 'sealed' keyword.",
+  );
+
   static const ParserErrorCode SETTER_CONSTRUCTOR = ParserErrorCode(
     'SETTER_CONSTRUCTOR',
     "Constructors can't be a setter.",
diff --git a/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart b/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
index fd10c92..6284ee2 100644
--- a/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
@@ -119,7 +119,7 @@
     DartType rightType, {
     required Map<DartType, NonPromotionReason> Function()? whyNotPromoted,
   }) {
-    if (!writeType.isVoid && _checkForUseOfVoidResult(right)) {
+    if (writeType is! VoidType && _checkForUseOfVoidResult(right)) {
       return;
     }
 
@@ -127,6 +127,20 @@
       return;
     }
 
+    if (writeType is RecordType) {
+      if (rightType is! RecordType && writeType.positionalFields.length == 1) {
+        var field = writeType.positionalFields.first;
+        if (_typeSystem.isAssignableTo(field.type, rightType)) {
+          _errorReporter.reportErrorForNode(
+            WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
+            right,
+            [],
+          );
+          return;
+        }
+      }
+    }
+
     _errorReporter.reportErrorForNode(
       CompileTimeErrorCode.INVALID_ASSIGNMENT,
       right,
@@ -193,7 +207,7 @@
     // Values of the type void cannot be used.
     // Example: `y += 0`, is not allowed.
     if (operatorType != TokenType.EQ) {
-      if (leftType.isVoid) {
+      if (leftType is VoidType) {
         _errorReporter.reportErrorForToken(
           CompileTimeErrorCode.USE_OF_VOID_RESULT,
           operator,
diff --git a/analyzer/lib/src/dart/resolver/ast_rewrite.dart b/analyzer/lib/src/dart/resolver/ast_rewrite.dart
index 89659f5..98e03a6 100644
--- a/analyzer/lib/src/dart/resolver/ast_rewrite.dart
+++ b/analyzer/lib/src/dart/resolver/ast_rewrite.dart
@@ -251,15 +251,21 @@
       return node;
     }
     var prefix = node.prefix;
-    var element = nameScope.lookup(prefix.name).getter;
-    if (element is InterfaceElement) {
+    var prefixElement = nameScope.lookup(prefix.name).getter;
+    if (parent is ConstantPattern && prefixElement is PrefixElement) {
+      final element = prefixElement.scope.lookup(node.identifier.name).getter;
+      if (element is TypeDefiningElement) {
+        return _toPatternTypeLiteral(parent, node);
+      }
+    }
+    if (prefixElement is InterfaceElement) {
       // Example:
       //     class C { C.named(); }
       //     C.named
       return _toConstructorReference_prefixed(
-          node: node, classElement: element);
-    } else if (element is TypeAliasElement) {
-      var aliasedType = element.aliasedType;
+          node: node, classElement: prefixElement);
+    } else if (prefixElement is TypeAliasElement) {
+      var aliasedType = prefixElement.aliasedType;
       if (aliasedType is InterfaceType) {
         // Example:
         //     class C { C.named(); }
@@ -376,6 +382,18 @@
     return node;
   }
 
+  AstNode simpleIdentifier(Scope nameScope, SimpleIdentifierImpl node) {
+    final parent = node.parent;
+    if (parent is ConstantPattern) {
+      final element = nameScope.lookup(node.name).getter;
+      if (element is TypeDefiningElement) {
+        return _toPatternTypeLiteral(parent, node);
+      }
+    }
+
+    return node;
+  }
+
   AstNode _instanceCreation_prefix_type_name({
     required MethodInvocationImpl node,
     required PrefixedIdentifierImpl typeNameIdentifier,
@@ -627,4 +645,20 @@
     NodeReplacer.replace(node, methodInvocation);
     return methodInvocation;
   }
+
+  TypeLiteralImpl _toPatternTypeLiteral(
+    ConstantPattern parent,
+    IdentifierImpl node,
+  ) {
+    final result = TypeLiteralImpl(
+      typeName: NamedTypeImpl(
+        name: node,
+        typeArguments: null,
+        question: null,
+      ),
+    );
+    result.staticType = _typeProvider.typeType;
+    NodeReplacer.replace(node, result, parent: parent);
+    return result;
+  }
 }
diff --git a/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart b/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
index 050a614..a4935f4 100644
--- a/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
@@ -139,8 +139,8 @@
 
     void reportNullComparison(SyntacticEntity start, SyntacticEntity end) {
       var errorCode = notEqual
-          ? HintCode.UNNECESSARY_NULL_COMPARISON_FALSE
-          : HintCode.UNNECESSARY_NULL_COMPARISON_TRUE;
+          ? WarningCode.UNNECESSARY_NULL_COMPARISON_FALSE
+          : WarningCode.UNNECESSARY_NULL_COMPARISON_TRUE;
       var offset = start.offset;
       _errorReporter.reportErrorForOffset(errorCode, offset, end.end - offset);
     }
diff --git a/analyzer/lib/src/dart/resolver/body_inference_context.dart b/analyzer/lib/src/dart/resolver/body_inference_context.dart
index 54d18dc..efa7636 100644
--- a/analyzer/lib/src/dart/resolver/body_inference_context.dart
+++ b/analyzer/lib/src/dart/resolver/body_inference_context.dart
@@ -127,11 +127,11 @@
     // If `R` is `void`, or the function literal is marked `async` and `R` is
     // `FutureOr<void>`, let `S` be `void`.
     if (_typeSystem.isNonNullableByDefault) {
-      if (R.isVoid ||
+      if (R is VoidType ||
           isAsynchronous &&
               R is InterfaceType &&
               R.isDartAsyncFutureOr &&
-              R.typeArguments[0].isVoid) {
+              R.typeArguments[0] is VoidType) {
         return VoidTypeImpl.instance;
       }
     }
diff --git a/analyzer/lib/src/dart/resolver/exit_detector.dart b/analyzer/lib/src/dart/resolver/exit_detector.dart
index 2d6e4cb..1aca5b8 100644
--- a/analyzer/lib/src/dart/resolver/exit_detector.dart
+++ b/analyzer/lib/src/dart/resolver/exit_detector.dart
@@ -504,6 +504,11 @@
       _visitStatements(node.statements);
 
   @override
+  bool visitSwitchPatternCase(SwitchPatternCase node) {
+    return _visitStatements(node.statements);
+  }
+
+  @override
   bool visitSwitchStatement(SwitchStatement node) {
     bool outerBreakValue = _enclosingBlockContainsBreak;
     _enclosingBlockContainsBreak = false;
diff --git a/analyzer/lib/src/dart/resolver/extension_member_resolver.dart b/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
index 0e5d140..6dd7018 100644
--- a/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
@@ -222,7 +222,7 @@
       substitution,
     );
 
-    if (receiverType.isVoid) {
+    if (receiverType is VoidType) {
       _errorReporter.reportErrorForNode(
           CompileTimeErrorCode.USE_OF_VOID_RESULT, receiverExpression);
     } else if (!_typeSystem.isAssignableTo(receiverType, extendedType)) {
diff --git a/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart b/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
index 331cb62..5168a31 100644
--- a/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart
@@ -48,29 +48,17 @@
     }
 
     var receiverType = function.typeOrThrow;
-    if (receiverType is InterfaceType) {
-      // Note: in this circumstance it's not necessary to call
-      // `_nullableDereferenceVerifier.expression` because
-      // `_resolveReceiverInterfaceType` calls `TypePropertyResolver.resolve`,
-      // which does the necessary null checking.
-      _resolveReceiverInterfaceType(
-          node, function, receiverType, whyNotPromotedList,
-          contextType: contextType);
-      return;
-    }
-
     if (_checkForUseOfVoidResult(function, receiverType)) {
       _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
           contextType: contextType);
       return;
     }
 
-    _nullableDereferenceVerifier.expression(
-      CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE,
-      function,
-    );
-
     if (receiverType is FunctionType) {
+      _nullableDereferenceVerifier.expression(
+        CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE,
+        function,
+      );
       _resolve(node, receiverType, whyNotPromotedList,
           contextType: contextType);
       return;
@@ -84,8 +72,40 @@
       return;
     }
 
-    _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
-        contextType: contextType);
+    var result = _typePropertyResolver.resolve(
+      receiver: function,
+      receiverType: receiverType,
+      name: FunctionElement.CALL_METHOD_NAME,
+      propertyErrorEntity: function,
+      nameErrorEntity: function,
+    );
+    var callElement = result.getter;
+
+    if (callElement == null) {
+      if (result.needsGetterError) {
+        _errorReporter.reportErrorForNode(
+          CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
+          function,
+        );
+      }
+      _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
+          contextType: contextType);
+      return;
+    }
+
+    if (callElement.kind != ElementKind.METHOD) {
+      _errorReporter.reportErrorForNode(
+        CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
+        function,
+      );
+      _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
+          contextType: contextType);
+      return;
+    }
+
+    node.staticElement = callElement;
+    var rawType = callElement.type;
+    _resolve(node, rawType, whyNotPromotedList, contextType: contextType);
   }
 
   /// Check for situations where the result of a method or function is used,
@@ -158,48 +178,6 @@
     _resolve(node, rawType, whyNotPromotedList, contextType: contextType);
   }
 
-  void _resolveReceiverInterfaceType(
-      FunctionExpressionInvocationImpl node,
-      Expression function,
-      InterfaceType receiverType,
-      List<WhyNotPromotedGetter> whyNotPromotedList,
-      {required DartType? contextType}) {
-    var result = _typePropertyResolver.resolve(
-      receiver: function,
-      receiverType: receiverType,
-      name: FunctionElement.CALL_METHOD_NAME,
-      propertyErrorEntity: function,
-      nameErrorEntity: function,
-    );
-    var callElement = result.getter;
-
-    if (callElement == null) {
-      if (result.needsGetterError) {
-        _errorReporter.reportErrorForNode(
-          CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
-          function,
-        );
-      }
-      _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
-          contextType: contextType);
-      return;
-    }
-
-    if (callElement.kind != ElementKind.METHOD) {
-      _errorReporter.reportErrorForNode(
-        CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
-        function,
-      );
-      _unresolved(node, DynamicTypeImpl.instance, whyNotPromotedList,
-          contextType: contextType);
-      return;
-    }
-
-    node.staticElement = callElement;
-    var rawType = callElement.type;
-    _resolve(node, rawType, whyNotPromotedList, contextType: contextType);
-  }
-
   void _unresolved(FunctionExpressionInvocationImpl node, DartType type,
       List<WhyNotPromotedGetter> whyNotPromotedList,
       {required DartType? contextType}) {
diff --git a/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart b/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart
index 580be8a..e29344f 100644
--- a/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart
+++ b/analyzer/lib/src/dart/resolver/legacy_type_asserter.dart
@@ -127,7 +127,7 @@
       return;
     }
 
-    if (type.isDynamic || type.isVoid) {
+    if (type.isDynamic || type is VoidType) {
       return;
     }
 
diff --git a/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart b/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart
index ec5a887..4edfbdf 100644
--- a/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/list_pattern_resolver.dart
@@ -30,8 +30,10 @@
       }
     }
 
-    node.requiredType = resolverVisitor.analyzeListPattern(context, node,
-        elementType: typeArguments?.arguments.first.typeOrThrow,
-        elements: node.elements);
+    node.requiredType = resolverVisitor
+        .analyzeListPattern(context, node,
+            elementType: typeArguments?.arguments.first.typeOrThrow,
+            elements: node.elements)
+        .requiredType;
   }
 }
diff --git a/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/analyzer/lib/src/dart/resolver/property_element_resolver.dart
index a4dc213..94ee843 100644
--- a/analyzer/lib/src/dart/resolver/property_element_resolver.dart
+++ b/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -77,7 +77,7 @@
     var targetType = target.typeOrThrow;
     targetType = _typeSystem.resolveToBound(targetType);
 
-    if (targetType.isVoid) {
+    if (targetType is VoidType) {
       // TODO(scheglov) Report directly in TypePropertyResolver?
       _reportUnresolvedIndex(
         node,
@@ -431,7 +431,7 @@
       );
     }
 
-    if (targetType.isVoid) {
+    if (targetType is VoidType) {
       errorReporter.reportErrorForNode(
         CompileTimeErrorCode.USE_OF_VOID_RESULT,
         propertyName,
diff --git a/analyzer/lib/src/dart/resolver/resolution_visitor.dart b/analyzer/lib/src/dart/resolver/resolution_visitor.dart
index 9a08bbb..d83363d 100644
--- a/analyzer/lib/src/dart/resolver/resolution_visitor.dart
+++ b/analyzer/lib/src/dart/resolver/resolution_visitor.dart
@@ -2,6 +2,8 @@
 // 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:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
+    as shared;
 import 'package:_fe_analyzer_shared/src/type_inference/variable_bindings.dart';
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/ast/ast.dart';
@@ -1155,6 +1157,16 @@
   }
 
   @override
+  void visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
+    final newNode = _astRewriter.simpleIdentifier(_nameScope, node);
+    if (newNode != node) {
+      return newNode.accept(this);
+    }
+
+    super.visitSimpleIdentifier(node);
+  }
+
+  @override
   void visitSuperFormalParameter(covariant SuperFormalParameterImpl node) {
     SuperFormalParameterElementImpl element;
     if (node.parent is DefaultFormalParameter) {
@@ -1699,7 +1711,7 @@
   JoinPatternVariableElementImpl joinPatternVariables({
     required Object key,
     required List<PromotableElement> components,
-    required bool isConsistent,
+    required shared.JoinedPatternVariableInconsistency inconsistency,
   }) {
     var first = components.first;
     List<PatternVariableElementImpl> expandedVariables;
@@ -1723,10 +1735,11 @@
       first.name,
       -1,
       expandedVariables,
-      isConsistent &&
-          components.every((element) =>
-              element is! JoinPatternVariableElementImpl ||
-              element.isConsistent),
+      inconsistency.maxWithAll(
+        components
+            .whereType<JoinPatternVariableElementImpl>()
+            .map((e) => e.inconsistency),
+      ),
     )..enclosingElement = first.enclosingElement;
   }
 }
@@ -1758,6 +1771,7 @@
         [name],
       ),
     );
+    duplicate.isDuplicate = true;
   }
 
   @override
diff --git a/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart b/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart
index 347957e..ca99c2f 100644
--- a/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart
+++ b/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart
@@ -22,25 +22,12 @@
 class SharedTypeAnalyzerErrors
     implements
         shared.TypeAnalyzerErrors<AstNode, Statement, Expression,
-            PromotableElement, DartType, DartPattern> {
+            PromotableElement, DartType, DartPattern, void> {
   final ErrorReporter _errorReporter;
 
   SharedTypeAnalyzerErrors(this._errorReporter);
 
   @override
-  void argumentTypeNotAssignable({
-    required Expression argument,
-    required DartType argumentType,
-    required DartType parameterType,
-  }) {
-    _errorReporter.reportErrorForNode(
-      CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE,
-      argument,
-      [argumentType, parameterType],
-    );
-  }
-
-  @override
   void assertInErrorRecovery() {}
 
   @override
@@ -175,15 +162,6 @@
   }
 
   @override
-  void nonExhaustiveSwitch(
-      {required AstNode node, required DartType scrutineeType}) {
-    _errorReporter.reportErrorForNode(
-        CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH,
-        node,
-        [scrutineeType, scrutineeType.toString()]);
-  }
-
-  @override
   void patternDoesNotAllowLate({required AstNode pattern}) {
     throw UnimplementedError('TODO(paulberry)');
   }
@@ -225,6 +203,19 @@
   }
 
   @override
+  void relationalPatternOperandTypeNotAssignable({
+    required covariant RelationalPatternImpl pattern,
+    required DartType operandType,
+    required DartType parameterType,
+  }) {
+    _errorReporter.reportErrorForNode(
+      CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE,
+      pattern.operand,
+      [operandType, parameterType, pattern.operator.lexeme],
+    );
+  }
+
+  @override
   void relationalPatternOperatorReturnTypeNotAssignableToBool({
     required covariant RelationalPatternImpl pattern,
     required DartType returnType,
diff --git a/analyzer/lib/src/error/base_or_final_type_verifier.dart b/analyzer/lib/src/error/base_or_final_type_verifier.dart
new file mode 100644
index 0000000..ae6cd9e
--- /dev/null
+++ b/analyzer/lib/src/error/base_or_final_type_verifier.dart
@@ -0,0 +1,241 @@
+// Copyright (c) 2023, 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:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/diagnostic/diagnostic.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/diagnostic/diagnostic.dart';
+import 'package:analyzer/src/error/codes.dart';
+
+/// Helper for verifying that subelements of a base or final element must be
+/// base, final, or sealed.
+class BaseOrFinalTypeVerifier {
+  final LibraryElement _definingLibrary;
+  final ErrorReporter _errorReporter;
+
+  BaseOrFinalTypeVerifier({
+    required LibraryElement definingLibrary,
+    required ErrorReporter errorReporter,
+  })  : _definingLibrary = definingLibrary,
+        _errorReporter = errorReporter;
+
+  /// Check to ensure the subelement of a base or final element must be base,
+  /// final, or sealed and that base elements are not implemented outside of its
+  /// library. Otherwise, an error is reported on that element.
+  ///
+  /// See [CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED],
+  /// [CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED],
+  /// [CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY].
+  void checkElement(
+      ClassOrMixinElementImpl element, ImplementsClause? implementsClause) {
+    final supertype = element.supertype;
+    if (supertype != null && _checkSupertypes([supertype], element)) {
+      return;
+    }
+    if (implementsClause != null &&
+        _checkInterfaceSupertypes(implementsClause.interfaces, element,
+            areImplementedInterfaces: true)) {
+      return;
+    }
+    if (_checkSupertypes(element.mixins, element)) {
+      return;
+    }
+    if (element is MixinElementImpl &&
+        _checkSupertypes(element.superclassConstraints, element,
+            areSuperclassConstraints: true)) {
+      return;
+    }
+  }
+
+  /// Returns true if a 'base' or 'final' subtype modifier error is reported for
+  /// an interface in [interfaces].
+  bool _checkInterfaceSupertypes(
+      List<NamedType> interfaces, ClassOrMixinElementImpl subElement,
+      {bool areImplementedInterfaces = false}) {
+    for (NamedType interface in interfaces) {
+      final interfaceType = interface.type;
+      if (interfaceType is InterfaceType) {
+        final interfaceElement = interfaceType.element;
+        if (interfaceElement is ClassOrMixinElementImpl) {
+          // Return early if an error has been reported to prevent reporting
+          // multiple errors on one element.
+          if (_reportRestrictionError(subElement, interfaceElement,
+              implementsNamedType: interface)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  /// Returns true if a 'base' or 'final' subtype modifier error is reported for
+  /// a supertype in [supertypes].
+  bool _checkSupertypes(
+      List<InterfaceType> supertypes, ClassOrMixinElementImpl subElement,
+      {bool areSuperclassConstraints = false}) {
+    for (final supertype in supertypes) {
+      final supertypeElement = supertype.element;
+      if (supertypeElement is ClassOrMixinElementImpl) {
+        // Return early if an error has been reported to prevent reporting
+        // multiple errors on one element.
+        if (_reportRestrictionError(subElement, supertypeElement,
+            isSuperclassConstraint: areSuperclassConstraints)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /// Returns the nearest explicitly declared 'base' or 'final' element in the
+  /// element hierarchy for [element].
+  ClassOrMixinElementImpl? _getExplicitlyBaseOrFinalElement(
+      ClassOrMixinElementImpl element) {
+    // The current element has an explicit 'base' or 'final' modifier.
+    if ((element.isBase || element.isFinal) && !element.isSealed) {
+      return element;
+    }
+
+    ClassOrMixinElementImpl? baseOrFinalSuperElement;
+    final supertype = element.supertype;
+    if (supertype != null) {
+      baseOrFinalSuperElement ??=
+          _getExplicitlyBaseOrFinalElementFromSuperTypes([supertype]);
+    }
+    baseOrFinalSuperElement ??=
+        _getExplicitlyBaseOrFinalElementFromSuperTypes(element.interfaces);
+    baseOrFinalSuperElement ??=
+        _getExplicitlyBaseOrFinalElementFromSuperTypes(element.mixins);
+    if (element is MixinElementImpl) {
+      baseOrFinalSuperElement ??=
+          _getExplicitlyBaseOrFinalElementFromSuperTypes(
+              element.superclassConstraints);
+    }
+    return baseOrFinalSuperElement;
+  }
+
+  /// Returns the first explicitly declared 'base' or 'final' element found in
+  /// the class hierarchies of a supertype in [supertypes], or `null` if there
+  /// is none.
+  ClassOrMixinElementImpl? _getExplicitlyBaseOrFinalElementFromSuperTypes(
+      List<InterfaceType> supertypes) {
+    ClassOrMixinElementImpl? baseOrFinalElement;
+    for (final supertype in supertypes) {
+      final supertypeElement = supertype.element;
+      if (supertypeElement is ClassOrMixinElementImpl) {
+        baseOrFinalElement = _getExplicitlyBaseOrFinalElement(supertypeElement);
+        if (baseOrFinalElement != null) {
+          return baseOrFinalElement;
+        }
+      }
+    }
+    return baseOrFinalElement;
+  }
+
+  /// Checks whether a `final`, `base` or `interface` modifier can be ignored.
+  ///
+  /// Checks whether a subclass in the current library
+  /// can ignore a class modifier of a declaration in [superLibrary].
+  ///
+  /// Only true if the supertype library is a platform library, and
+  /// either the current library is also a platform library,
+  /// or the current library has a language version which predates
+  /// class modifiers.
+  bool _mayIgnoreClassModifiers(LibraryElement superLibrary) {
+    // Only modifiers in platform libraries can be ignored.
+    if (!superLibrary.isInSdk) return false;
+
+    // Other platform libraries can ignore modifiers.
+    if (_definingLibrary.isInSdk) return true;
+
+    // Libraries predating class modifiers can ignore platform modifiers.
+    return !_definingLibrary.featureSet.isEnabled(Feature.class_modifiers);
+  }
+
+  /// Returns true if a element modifier restriction error has been reported.
+  ///
+  /// Reports an error based on the modifier of the [superElement].
+  bool _reportRestrictionError(
+      ClassOrMixinElementImpl element, ClassOrMixinElementImpl superElement,
+      {bool isSuperclassConstraint = false, NamedType? implementsNamedType}) {
+    ClassOrMixinElementImpl? baseOrFinalSuperElement;
+    if (superElement.isBase || superElement.isFinal) {
+      // The 'base' or 'final' modifier may be an induced modifier. Find the
+      // explicitly declared 'base' or 'final' in the hierarchy.
+      baseOrFinalSuperElement = _getExplicitlyBaseOrFinalElement(superElement);
+    } else {
+      // There are no restrictions on this element's modifiers.
+      return false;
+    }
+
+    if (element.library != _definingLibrary) {
+      // Only report errors on elements within the current library.
+      return false;
+    }
+    if (baseOrFinalSuperElement != null &&
+        !_mayIgnoreClassModifiers(baseOrFinalSuperElement.library)) {
+      // The context message links to the explicitly declared 'base' or 'final'
+      // super element and is only added onto the error if 'base' or 'final' is
+      // an induced modifier of the direct super element.
+      final contextMessage = <DiagnosticMessage>[
+        DiagnosticMessageImpl(
+          filePath: baseOrFinalSuperElement.source.fullName,
+          length: baseOrFinalSuperElement.nameLength,
+          message: "The type '${superElement.displayName}' is a subtype of "
+              "'${baseOrFinalSuperElement.displayName}', and "
+              "'${baseOrFinalSuperElement.displayName}' is defined here.",
+          offset: baseOrFinalSuperElement.nameOffset,
+          url: null,
+        )
+      ];
+
+      // It's an error to implement a class if it has a supertype from a
+      // different library which is marked base.
+      if (implementsNamedType != null &&
+          superElement.isSealed &&
+          baseOrFinalSuperElement.library != element.library) {
+        if (baseOrFinalSuperElement.isBase) {
+          _errorReporter.reportErrorForNode(
+              CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
+              implementsNamedType,
+              [baseOrFinalSuperElement.displayName],
+              contextMessage);
+          return true;
+        }
+      }
+
+      if (!element.isBase && !element.isFinal && !element.isSealed) {
+        if (baseOrFinalSuperElement.isFinal) {
+          if (!isSuperclassConstraint &&
+              baseOrFinalSuperElement.library != element.library) {
+            // If you can't extend, implement or mix in a final element outside of
+            // its library anyways, it's not helpful to report a subelement
+            // modifier error.
+            return false;
+          }
+          _errorReporter.reportErrorForElement(
+              CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,
+              element,
+              [element.displayName, baseOrFinalSuperElement.displayName],
+              superElement.isSealed ? contextMessage : null);
+          return true;
+        } else if (baseOrFinalSuperElement.isBase) {
+          _errorReporter.reportErrorForElement(
+              CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
+              element,
+              [element.displayName, baseOrFinalSuperElement.displayName],
+              superElement.isSealed ? contextMessage : null);
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+}
diff --git a/analyzer/lib/src/error/best_practices_verifier.dart b/analyzer/lib/src/error/best_practices_verifier.dart
index 54d0b14..44546be 100644
--- a/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/analyzer/lib/src/error/best_practices_verifier.dart
@@ -12,6 +12,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
@@ -291,8 +292,9 @@
         }
       } else {
         // Something other than a declaration was annotated. Whatever this is,
-        // it probably warrants a Hint, but this has not been specified on
-        // visibleForTemplate or visibleForTesting, so leave it alone for now.
+        // it probably warrants a Warning, but this has not been specified on
+        // `visibleForTemplate` or `visibleForTesting`, so leave it alone for
+        // now.
       }
     }
 
@@ -312,7 +314,7 @@
               ? undefinedParam.value
               : undefinedParam.staticParameterElement?.name;
           _errorReporter.reportErrorForNode(
-              HintCode.UNDEFINED_REFERENCED_PARAMETER,
+              WarningCode.UNDEFINED_REFERENCED_PARAMETER,
               undefinedParam,
               [paramName ?? undefinedParam, name]);
         }
@@ -379,6 +381,20 @@
   }
 
   @override
+  void visitCastPattern(CastPattern node) {
+    var type = node.type.type;
+    var matchedValueType = node.matchedValueType;
+    if (type != null &&
+        _typeSystem.isNonNullable(type) &&
+        matchedValueType != null &&
+        matchedValueType.isDartCoreNull) {
+      _errorReporter.reportErrorForNode(
+          WarningCode.CAST_FROM_NULL_ALWAYS_FAILS, node);
+    }
+    super.visitCastPattern(node);
+  }
+
+  @override
   void visitCatchClause(CatchClause node) {
     super.visitCatchClause(node);
     _checkForNullableTypeInCatchClause(node);
@@ -397,8 +413,6 @@
     }
 
     try {
-      // Commented out until we decide that we want this hint in the analyzer
-      //    checkForOverrideEqualsButNotHashCode(node);
       _checkForImmutable(node);
       _checkForInvalidSealedSuperclass(node);
       super.visitClassDeclaration(node);
@@ -429,11 +443,22 @@
   }
 
   @override
+  void visitConstantPattern(ConstantPattern node) {
+    if (node.expression.isDoubleNan) {
+      _errorReporter.reportErrorForNode(
+        WarningCode.UNNECESSARY_NAN_COMPARISON_FALSE,
+        node,
+      );
+    }
+    super.visitConstantPattern(node);
+  }
+
+  @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
     var element = node.declaredElement as ConstructorElementImpl;
     if (!_isNonNullableByDefault && element.isFactory) {
       if (node.body is BlockFunctionBody) {
-        // Check the block for a return statement, if not, create the hint.
+        // Check the block for a return statement.
         if (!ExitDetector.exits(node.body)) {
           _errorReporter.reportErrorForNode(
               WarningCode.MISSING_RETURN, node, [node.returnType.name]);
@@ -476,6 +501,18 @@
   }
 
   @override
+  void visitEnumDeclaration(EnumDeclaration node) {
+    _deprecatedVerifier
+        .pushInDeprecatedValue(node.declaredElement!.hasDeprecated);
+
+    try {
+      super.visitEnumDeclaration(node);
+    } finally {
+      _deprecatedVerifier.popInDeprecated();
+    }
+  }
+
+  @override
   void visitExportDirective(ExportDirective node) {
     _deprecatedVerifier.exportDirective(node);
     _checkForInternalExport(node);
@@ -491,6 +528,18 @@
   }
 
   @override
+  void visitExtensionDeclaration(ExtensionDeclaration node) {
+    _deprecatedVerifier
+        .pushInDeprecatedValue(node.declaredElement!.hasDeprecated);
+
+    try {
+      super.visitExtensionDeclaration(node);
+    } finally {
+      _deprecatedVerifier.popInDeprecated();
+    }
+  }
+
+  @override
   void visitFieldDeclaration(FieldDeclaration node) {
     _deprecatedVerifier.pushInDeprecatedMetadata(node.metadata);
 
@@ -679,8 +728,6 @@
       _inDoNotStoreMember = true;
     }
     try {
-      // This was determined to not be a good hint, see: dartbug.com/16029
-      //checkForOverridingPrivateMember(node);
       _checkForMissingReturn(node.body, node);
       _mustCallSuperVerifier.checkMethodDeclaration(node);
       _checkForUnnecessaryNoSuchMethod(node);
@@ -725,7 +772,7 @@
   @override
   void visitMethodInvocation(MethodInvocation node) {
     _deprecatedVerifier.methodInvocation(node);
-    _checkForNullAwareHints(node, node.operator);
+    _checkForNullAwareWarnings(node, node.operator);
     _errorHandlerVerifier.verifyMethodInvocation(node);
     _nullSafeApiVerifier.methodInvocation(node);
     super.visitMethodInvocation(node);
@@ -762,18 +809,25 @@
               (type.isDynamic && name == 'dynamic')) &&
           type.alias == null) {
         _errorReporter.reportErrorForToken(
-            HintCode.UNNECESSARY_QUESTION_MARK, question, [name]);
+            WarningCode.UNNECESSARY_QUESTION_MARK, question, [name]);
       }
     }
     super.visitNamedType(node);
   }
 
   @override
+  void visitPatternField(PatternField node) {
+    _invalidAccessVerifier.verifyPatternField(node as PatternFieldImpl);
+    super.visitPatternField(node);
+  }
+
+  @override
   void visitPostfixExpression(PostfixExpression node) {
     _deprecatedVerifier.postfixExpression(node);
     if (node.operator.type == TokenType.BANG &&
         node.operand.typeOrThrow.isDartCoreNull) {
-      _errorReporter.reportErrorForNode(HintCode.NULL_CHECK_ALWAYS_FAILS, node);
+      _errorReporter.reportErrorForNode(
+          WarningCode.NULL_CHECK_ALWAYS_FAILS, node);
     }
     super.visitPostfixExpression(node);
   }
@@ -786,7 +840,7 @@
 
   @override
   void visitPropertyAccess(PropertyAccess node) {
-    _checkForNullAwareHints(node, node.operator);
+    _checkForNullAwareWarnings(node, node.operator);
     super.visitPropertyAccess(node);
   }
 
@@ -848,14 +902,15 @@
     }
   }
 
-  /// Check for the passed is expression for the unnecessary type check hint
-  /// codes as well as null checks expressed using an is expression.
+  /// Checks for the passed [IsExpression] for the unnecessary type check
+  /// warning codes as well as null checks expressed using an
+  /// [IsExpression].
   ///
-  /// @param node the is expression to check
-  /// @return `true` if and only if a hint code is generated on the passed node
-  /// See [HintCode.TYPE_CHECK_IS_NOT_NULL], [HintCode.TYPE_CHECK_IS_NULL],
-  /// [HintCode.UNNECESSARY_TYPE_CHECK_TRUE], and
-  /// [HintCode.UNNECESSARY_TYPE_CHECK_FALSE].
+  /// Returns `true` if a warning code is generated on [node].
+  /// See [WarningCode.TYPE_CHECK_IS_NOT_NULL],
+  /// [WarningCode.TYPE_CHECK_IS_NULL],
+  /// [WarningCode.UNNECESSARY_TYPE_CHECK_TRUE], and
+  /// [WarningCode.UNNECESSARY_TYPE_CHECK_FALSE].
   bool _checkAllTypeChecks(IsExpression node) {
     var leftNode = node.expression;
     var rightNode = node.type;
@@ -864,8 +919,8 @@
     void report() {
       _errorReporter.reportErrorForNode(
         node.notOperator == null
-            ? HintCode.UNNECESSARY_TYPE_CHECK_TRUE
-            : HintCode.UNNECESSARY_TYPE_CHECK_FALSE,
+            ? WarningCode.UNNECESSARY_TYPE_CHECK_TRUE
+            : WarningCode.UNNECESSARY_TYPE_CHECK_FALSE,
         node,
       );
     }
@@ -928,7 +983,7 @@
       // named elements, so we can safely assume `entry.value.name` is
       // non-`null`.
       _errorReporter.reportErrorForNode(
-        HintCode.ASSIGNMENT_OF_DO_NOT_STORE,
+        WarningCode.ASSIGNMENT_OF_DO_NOT_STORE,
         entry.key,
         [entry.value.name!],
       );
@@ -1004,7 +1059,7 @@
   ///
   /// If [node] is marked with [immutable] or inherits from a class or mixin
   /// marked with [immutable], this function searches the fields of [node] and
-  /// its superclasses, reporting a hint if any non-final instance fields are
+  /// its superclasses, reporting a warning if any non-final instance fields are
   /// found.
   void _checkForImmutable(NamedCompilationUnitMember node) {
     /// Return `true` if the given class [element] is annotated with the
@@ -1200,12 +1255,14 @@
               element.superclassConstraints.contains(supertype)) {
             // This is a special violation of the sealed class contract,
             // requiring specific messaging.
-            _errorReporter.reportErrorForNode(HintCode.MIXIN_ON_SEALED_CLASS,
+            _errorReporter.reportErrorForNode(WarningCode.MIXIN_ON_SEALED_CLASS,
                 node, [superclass.name.toString()]);
           } else {
             // This is a regular violation of the sealed class contract.
-            _errorReporter.reportErrorForNode(HintCode.SUBTYPE_OF_SEALED_CLASS,
-                node, [superclass.name.toString()]);
+            _errorReporter.reportErrorForNode(
+                WarningCode.SUBTYPE_OF_SEALED_CLASS,
+                node,
+                [superclass.name.toString()]);
           }
         }
       }
@@ -1214,7 +1271,7 @@
 
   void _checkForInvariantNanComparison(BinaryExpression node) {
     void reportStartEnd(
-      HintCode errorCode,
+      ErrorCode errorCode,
       SyntacticEntity startEntity,
       SyntacticEntity endEntity,
     ) {
@@ -1226,23 +1283,18 @@
       );
     }
 
-    bool isDoubleNan(Expression expression) =>
-        expression is PrefixedIdentifier &&
-        expression.prefix.name == 'double' &&
-        expression.identifier.name == 'nan';
-
-    void checkLeftRight(HintCode errorCode) {
-      if (isDoubleNan(node.leftOperand)) {
+    void checkLeftRight(ErrorCode errorCode) {
+      if (node.leftOperand.isDoubleNan) {
         reportStartEnd(errorCode, node.leftOperand, node.operator);
-      } else if (isDoubleNan(node.rightOperand)) {
+      } else if (node.rightOperand.isDoubleNan) {
         reportStartEnd(errorCode, node.operator, node.rightOperand);
       }
     }
 
     if (node.operator.type == TokenType.BANG_EQ) {
-      checkLeftRight(HintCode.UNNECESSARY_NAN_COMPARISON_TRUE);
+      checkLeftRight(WarningCode.UNNECESSARY_NAN_COMPARISON_TRUE);
     } else if (node.operator.type == TokenType.EQ_EQ) {
-      checkLeftRight(HintCode.UNNECESSARY_NAN_COMPARISON_FALSE);
+      checkLeftRight(WarningCode.UNNECESSARY_NAN_COMPARISON_FALSE);
     }
   }
 
@@ -1250,7 +1302,7 @@
     if (!_isNonNullableByDefault) return;
 
     void reportStartEnd(
-      HintCode errorCode,
+      ErrorCode errorCode,
       SyntacticEntity startEntity,
       SyntacticEntity endEntity,
     ) {
@@ -1262,7 +1314,7 @@
       );
     }
 
-    void checkLeftRight(HintCode errorCode) {
+    void checkLeftRight(ErrorCode errorCode) {
       if (node.leftOperand is NullLiteral) {
         var rightType = node.rightOperand.typeOrThrow;
         if (_typeSystem.isStrictlyNonNullable(rightType)) {
@@ -1279,9 +1331,9 @@
     }
 
     if (node.operator.type == TokenType.BANG_EQ) {
-      checkLeftRight(HintCode.UNNECESSARY_NULL_COMPARISON_TRUE);
+      checkLeftRight(WarningCode.UNNECESSARY_NULL_COMPARISON_TRUE);
     } else if (node.operator.type == TokenType.EQ_EQ) {
-      checkLeftRight(HintCode.UNNECESSARY_NULL_COMPARISON_FALSE);
+      checkLeftRight(WarningCode.UNNECESSARY_NULL_COMPARISON_FALSE);
     }
   }
 
@@ -1304,10 +1356,10 @@
       if (constructorName.name != null) {
         fullConstructorName = '$fullConstructorName.${constructorName.name}';
       }
-      HintCode hint = node.keyword?.keyword == Keyword.NEW
-          ? HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW
-          : HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
-      _errorReporter.reportErrorForNode(hint, node, [fullConstructorName]);
+      var warning = node.keyword?.keyword == Keyword.NEW
+          ? WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW
+          : WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
+      _errorReporter.reportErrorForNode(warning, node, [fullConstructorName]);
     }
   }
 
@@ -1337,13 +1389,10 @@
     return false;
   }
 
-  /// Generate a hint for functions or methods that have a return type, but do
-  /// not have a return statement on all branches. At the end of blocks with no
-  /// return, Dart implicitly returns `null`. Avoiding these implicit returns
-  /// is considered a best practice.
-  ///
-  /// Note: for async functions/methods, this hint only applies when the
-  /// function has a return type that Future<Null> is not assignable to.
+  /// Generates a warning for functions that have a potentially non-nullable
+  /// return type, but do not have a return statement on all branches. At the
+  /// end of blocks with no return, Dart implicitly returns `null`. Avoiding
+  /// these implicit returns is considered a best practice.
   ///
   /// See [WarningCode.MISSING_RETURN].
   void _checkForMissingReturn(FunctionBody body, AstNode functionNode) {
@@ -1411,8 +1460,8 @@
     }
   }
 
-  /// Produce several null-aware related hints.
-  void _checkForNullAwareHints(Expression node, Token? operator) {
+  /// Produce several null-aware related warnings.
+  void _checkForNullAwareWarnings(Expression node, Token? operator) {
     if (_isNonNullableByDefault) {
       return;
     }
@@ -1511,10 +1560,10 @@
     }
   }
 
-  /// Generate a hint for `noSuchMethod` methods that do nothing except of
-  /// calling another `noSuchMethod` that is not defined by `Object`.
+  /// Generates a warning for `noSuchMethod` methods that do nothing except of
+  /// calling another `noSuchMethod` which is not defined by `Object`.
   ///
-  /// Return `true` if and only if a hint code is generated on the passed node.
+  /// Returns `true` if a warning code is generated for [node].
   bool _checkForUnnecessaryNoSuchMethod(MethodDeclaration node) {
     if (node.name.lexeme != FunctionElement.NO_SUCH_METHOD_METHOD_NAME) {
       return false;
@@ -1539,7 +1588,7 @@
     if (body is ExpressionFunctionBody) {
       if (isNonObjectNoSuchMethodInvocation(body.expression)) {
         _errorReporter.reportErrorForToken(
-            HintCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
+            WarningCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
         return true;
       }
     } else if (body is BlockFunctionBody) {
@@ -1549,7 +1598,7 @@
         if (returnStatement is ReturnStatement &&
             isNonObjectNoSuchMethodInvocation(returnStatement.expression)) {
           _errorReporter.reportErrorForToken(
-              HintCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
+              WarningCode.UNNECESSARY_NO_SUCH_METHOD, node.name);
           return true;
         }
       }
@@ -1576,12 +1625,13 @@
       if (returnType == null) return;
 
       bool isReturnVoid;
-      if (returnType.isVoid) {
+      if (returnType is VoidType) {
         isReturnVoid = true;
       } else if (returnType is ParameterizedType &&
           (returnType.isDartAsyncFuture || returnType.isDartAsyncFutureOr)) {
         var typeArguments = returnType.typeArguments;
-        isReturnVoid = typeArguments.length == 1 && typeArguments.first.isVoid;
+        isReturnVoid =
+            typeArguments.length == 1 && typeArguments.first is VoidType;
       } else {
         isReturnVoid = false;
       }
@@ -1591,7 +1641,7 @@
           var elements = expression.elements;
           if (elements.length == 1 && elements.first is Expression) {
             _errorReporter.reportErrorForNode(
-                HintCode.UNNECESSARY_SET_LITERAL, expression);
+                WarningCode.UNNECESSARY_SET_LITERAL, expression);
           }
         }
       }
@@ -1959,18 +2009,18 @@
       : _inTemplateSource =
             _library.source.fullName.contains(_templateExtension);
 
-  /// Produces a hint if [identifier] is accessed from an invalid location.
+  /// Produces a warning if [identifier] is accessed from an invalid location.
   ///
-  /// In particular, a hint is produced in either of the two following cases:
+  /// In particular, a warning is produced in either of the two following cases:
   ///
   /// * The element associated with [identifier] is annotated with [internal],
   ///   and is accessed from outside the package in which the element is
   ///   declared.
   /// * The element associated with [identifier] is annotated with [protected],
-  ///   [visibleForTesting], and/or [visibleForTemplate], and is accessed from a
+  ///   [visibleForTesting], and/or `visibleForTemplate`, and is accessed from a
   ///   location which is invalid as per the rules of each such annotation.
   ///   Conversely, if the element is annotated with more than one of these
-  ///   annotations, the access is valid (and no hint will be produced) if it
+  ///   annotations, the access is valid (and no warning is produced) if it
   ///   conforms to the rules of at least one of the annotations.
   void verify(SimpleIdentifier identifier) {
     if (identifier.inDeclarationContext() || _inCommentReference(identifier)) {
@@ -2035,6 +2085,30 @@
     }
   }
 
+  void verifyPatternField(PatternFieldImpl node) {
+    var element = node.element;
+    if (element == null || _inCurrentLibrary(element)) {
+      return;
+    }
+
+    if (_hasInternal(element) &&
+        !_isLibraryInWorkspacePackage(element.library)) {
+      var fieldName = node.name;
+      if (fieldName == null) {
+        return;
+      }
+      var errorEntity = node.errorEntity;
+
+      _errorReporter.reportErrorForOffset(
+          WarningCode.INVALID_USE_OF_INTERNAL_MEMBER,
+          errorEntity.offset,
+          errorEntity.length,
+          [element.displayName]);
+    }
+
+    _checkForOtherInvalidAccess(node, element);
+  }
+
   void verifySuperConstructorInvocation(SuperConstructorInvocation node) {
     if (node.constructorName != null) {
       // Named constructor calls are handled by [verify].
@@ -2048,8 +2122,7 @@
     }
   }
 
-  void _checkForInvalidInternalAccess(
-      SimpleIdentifier identifier, Element element) {
+  void _checkForInvalidInternalAccess(Identifier identifier, Element element) {
     if (_hasInternal(element) &&
         !_isLibraryInWorkspacePackage(element.library)) {
       String name;
@@ -2070,8 +2143,7 @@
     }
   }
 
-  void _checkForOtherInvalidAccess(
-      SimpleIdentifier identifier, Element element) {
+  void _checkForOtherInvalidAccess(AstNode node, Element element) {
     bool hasProtected = _hasProtected(element);
     if (hasProtected) {
       var definingClass = element.enclosingElement as InterfaceElement;
@@ -2080,16 +2152,16 @@
       }
     }
 
-    bool hasVisibleForTemplate = _hasVisibleForTemplate(element);
-    if (hasVisibleForTemplate) {
-      if (_inTemplateSource || _inExportDirective(identifier)) {
+    bool isVisibleForTemplateApplied = _isVisibleForTemplateApplied(element);
+    if (isVisibleForTemplateApplied) {
+      if (_inTemplateSource || _inExportDirective(node)) {
         return;
       }
     }
 
     bool hasVisibleForTesting = _hasVisibleForTesting(element);
     if (hasVisibleForTesting) {
-      if (_inTestDirectory || _inExportDirective(identifier)) {
+      if (_inTestDirectory || _inExportDirective(node)) {
         return;
       }
     }
@@ -2101,37 +2173,49 @@
     // annotation present.
 
     String name;
-    AstNode node;
+    SyntacticEntity errorEntity = node;
 
-    var grandparent = identifier.parent?.parent;
-
-    if (grandparent is ConstructorName) {
-      name = grandparent.toSource();
-      node = grandparent;
+    var grandparent = node.parent?.parent;
+    if (node is Identifier) {
+      if (grandparent is ConstructorName) {
+        name = grandparent.toSource();
+        errorEntity = grandparent;
+      } else {
+        name = node.name;
+      }
+    } else if (node is PatternFieldImpl) {
+      name = element.displayName;
+      errorEntity = node.errorEntity;
     } else {
-      name = identifier.name;
-      node = identifier;
+      throw StateError('Can only handle Identifier or PatternField, but got '
+          '${node.runtimeType}');
     }
 
     var definingClass = element.enclosingElement;
-    if (hasProtected) {
-      _errorReporter.reportErrorForNode(
-          WarningCode.INVALID_USE_OF_PROTECTED_MEMBER,
-          node,
-          [name, definingClass!.source!.uri]);
+    if (definingClass == null) {
+      return;
     }
-    if (hasVisibleForTemplate) {
-      _errorReporter.reportErrorForNode(
+    if (hasProtected) {
+      _errorReporter.reportErrorForOffset(
+          WarningCode.INVALID_USE_OF_PROTECTED_MEMBER,
+          errorEntity.offset,
+          errorEntity.length,
+          [name, definingClass.source!.uri]);
+    }
+    if (isVisibleForTemplateApplied) {
+      _errorReporter.reportErrorForOffset(
           WarningCode.INVALID_USE_OF_VISIBLE_FOR_TEMPLATE_MEMBER,
-          node,
-          [name, definingClass!.source!.uri]);
+          errorEntity.offset,
+          errorEntity.length,
+          [name, definingClass.source!.uri]);
     }
 
     if (hasVisibleForTesting) {
-      _errorReporter.reportErrorForNode(
+      _errorReporter.reportErrorForOffset(
           WarningCode.INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER,
-          node,
-          [name, definingClass!.source!.uri]);
+          errorEntity.offset,
+          errorEntity.length,
+          [name, definingClass.source!.uri]);
     }
 
     if (hasVisibleForOverriding) {
@@ -2141,14 +2225,15 @@
           parent is PropertyAccess && parent.target is SuperExpression) {
         var methodDeclaration =
             grandparent?.thisOrAncestorOfType<MethodDeclaration>();
-        if (methodDeclaration?.name.lexeme == identifier.name) {
+        if (methodDeclaration?.name.lexeme == name) {
           validOverride = true;
         }
       }
       if (!validOverride) {
-        _errorReporter.reportErrorForNode(
+        _errorReporter.reportErrorForOffset(
             WarningCode.INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER,
-            node,
+            errorEntity.offset,
+            errorEntity.length,
             [name]);
       }
     }
@@ -2215,6 +2300,10 @@
         element.variable.hasVisibleForTemplate) {
       return true;
     }
+    final enclosingElement = element.enclosingElement;
+    if (_hasVisibleForTemplate(enclosingElement)) {
+      return true;
+    }
     return false;
   }
 
@@ -2236,9 +2325,8 @@
 
   bool _inCurrentLibrary(Element element) => element.library == _library;
 
-  bool _inExportDirective(SimpleIdentifier identifier) =>
-      identifier.parent is Combinator &&
-      identifier.parent!.parent is ExportDirective;
+  bool _inExportDirective(AstNode node) =>
+      node.parent is Combinator && node.parent!.parent is ExportDirective;
 
   bool _isLibraryInWorkspacePackage(LibraryElement? library) {
     if (_workspacePackage == null || library == null) {
@@ -2248,6 +2336,21 @@
     }
     return _workspacePackage!.contains(library.source);
   }
+
+  /// Check if @visibleForTemplate is applied to the given [Element].
+  ///
+  /// [ClassElement] and [EnumElement] are excluded from the @visibleForTemplate
+  /// access checks. Instead, the access restriction is cascaded to the
+  /// corresponding class members and enum constants. For other types of
+  /// elements, check if they are annotated based on `hasVisibleForTemplate`
+  /// value.
+  bool _isVisibleForTemplateApplied(Element? element) {
+    if (element is ClassElement || element is EnumElement) {
+      return false;
+    } else {
+      return _hasVisibleForTemplate(element);
+    }
+  }
 }
 
 /// A visitor that determines, upon visiting a function body and/or a
@@ -2273,3 +2376,15 @@
     }
   }
 }
+
+extension on Expression {
+  /// Whether this is the [PrefixedIdentifier] referring to `double.nan`.
+  // TODO(srawlins): This will return the wrong answer for `prefixed.double.nan`
+  // and for `import 'foo.dart' as double; double.nan`.
+  bool get isDoubleNan {
+    final self = this;
+    return self is PrefixedIdentifier &&
+        self.prefix.name == 'double' &&
+        self.identifier.name == 'nan';
+  }
+}
diff --git a/analyzer/lib/src/error/codes.g.dart b/analyzer/lib/src/error/codes.g.dart
index 220cacc..0369aa1 100644
--- a/analyzer/lib/src/error/codes.g.dart
+++ b/analyzer/lib/src/error/codes.g.dart
@@ -415,8 +415,8 @@
   ///  0: the name of the class being used as a mixin
   static const CompileTimeErrorCode CLASS_USED_AS_MIXIN = CompileTimeErrorCode(
     'CLASS_USED_AS_MIXIN',
-    "The class '{0}' can't be used as a mixin because it isn't a mixin class "
-        "nor a mixin.",
+    "The class '{0}' can't be used as a mixin because it's neither a mixin "
+        "class nor a mixin.",
   );
 
   static const CompileTimeErrorCode CONCRETE_CLASS_HAS_ENUM_SUPERINTERFACE =
@@ -2140,18 +2140,6 @@
   );
 
   ///  Parameters:
-  ///  0: the name of the pattern variable
-  static const CompileTimeErrorCode
-      INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE = CompileTimeErrorCode(
-    'INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE',
-    "The variable '{0}' doesn't have the same type and/or finality in all "
-        "cases that share this body.",
-    correctionMessage:
-        "Try declaring the variable pattern with the same type and finality in "
-        "all cases.",
-  );
-
-  ///  Parameters:
   ///  0: the name of the initializing formal that is not an instance variable in
   ///     the immediately enclosing class
   static const CompileTimeErrorCode INITIALIZER_FOR_NON_EXISTENT_FIELD =
@@ -3016,6 +3004,15 @@
   );
 
   ///  Parameters:
+  ///  0: the name of the mixin class that is invalid
+  static const CompileTimeErrorCode MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT =
+      CompileTimeErrorCode(
+    'MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT',
+    "The class '{0}' can't be declared a mixin because it extends a class "
+        "other than 'Object'.",
+  );
+
+  ///  Parameters:
   ///  0: the name of the mixin that is invalid
   static const CompileTimeErrorCode MIXIN_CLASS_DECLARES_CONSTRUCTOR =
       CompileTimeErrorCode(
@@ -3470,7 +3467,7 @@
       CompileTimeErrorCode(
     'NON_EXHAUSTIVE_SWITCH',
     "The type '{0}' is not exhaustively matched by the switch cases.",
-    correctionMessage: "Try adding a default case or cases that match {1}.",
+    correctionMessage: "Try adding a default case or cases that match '{1}'.",
   );
 
   ///  No parameters.
@@ -3876,11 +3873,11 @@
     hasPublishedDocs: true,
   );
 
+  ///  No parameters.
   static const CompileTimeErrorCode PATTERN_ASSIGNMENT_NOT_LOCAL_VARIABLE =
       CompileTimeErrorCode(
     'PATTERN_ASSIGNMENT_NOT_LOCAL_VARIABLE',
-    "Only local variables or formal parameters can be used in pattern "
-        "assignments.",
+    "Only local variables can be assigned in pattern assignments.",
     correctionMessage: "Try assigning to a local variable.",
   );
 
@@ -3913,6 +3910,46 @@
     correctionMessage: "Try assigning to a different variable.",
   );
 
+  ///  Parameters:
+  ///  0: the name of the pattern variable
+  static const CompileTimeErrorCode
+      PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE =
+      CompileTimeErrorCode(
+    'INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE',
+    "The variable '{0}' doesn't have the same type and/or finality in all "
+        "cases that share this body.",
+    correctionMessage:
+        "Try declaring the variable pattern with the same type and finality in "
+        "all cases.",
+    uniqueName: 'PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE',
+  );
+
+  ///  Parameters:
+  ///  0: the name of the pattern variable
+  static const CompileTimeErrorCode
+      PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL = CompileTimeErrorCode(
+    'INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE',
+    "The variable '{0}' is not available because there is a label or 'default' "
+        "case.",
+    correctionMessage:
+        "Try removing the label, or providing the 'default' case with its own "
+        "body.",
+    uniqueName: 'PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL',
+  );
+
+  ///  Parameters:
+  ///  0: the name of the pattern variable
+  static const CompileTimeErrorCode
+      PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES = CompileTimeErrorCode(
+    'INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE',
+    "The variable '{0}' is available in some, but not all cases that share "
+        "this body.",
+    correctionMessage:
+        "Try declaring the variable pattern with the same type and finality in "
+        "all cases.",
+    uniqueName: 'PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES',
+  );
+
   ///  No parameters.
   static const CompileTimeErrorCode
       POSITIONAL_SUPER_FORMAL_PARAMETER_WITH_POSITIONAL_ARGUMENT =
@@ -4240,6 +4277,17 @@
         "instead.",
   );
 
+  ///  Parameters:
+  ///  0: the operand type
+  ///  1: the parameter type of the invoked operator
+  ///  2: the name of the invoked operator
+  static const CompileTimeErrorCode
+      RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE = CompileTimeErrorCode(
+    'RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE',
+    "The constant expression type '{0}' is not assignable to the parameter "
+        "type '{1}' of the '{2}' operator.",
+  );
+
   static const CompileTimeErrorCode
       RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL =
       CompileTimeErrorCode(
@@ -4423,6 +4471,28 @@
   );
 
   ///  Parameters:
+  ///  0: the name of the subtype that is not 'base', 'final', or 'sealed'
+  ///  1: the name of the 'base' supertype
+  static const CompileTimeErrorCode
+      SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED = CompileTimeErrorCode(
+    'SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED',
+    "The type '{0}' must be 'base', 'final' or 'sealed' because the supertype "
+        "'{1}' is 'base'.",
+    uniqueName: 'SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED',
+  );
+
+  ///  Parameters:
+  ///  0: the name of the subtype that is not 'base', 'final', or 'sealed'
+  ///  1: the name of the 'final' supertype
+  static const CompileTimeErrorCode
+      SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED = CompileTimeErrorCode(
+    'SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED',
+    "The type '{0}' must be 'base', 'final' or 'sealed' because the supertype "
+        "'{1}' is 'final'.",
+    uniqueName: 'SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED',
+  );
+
+  ///  Parameters:
   ///  0: the type of super-parameter
   ///  1: the type of associated super-constructor parameter
   static const CompileTimeErrorCode
@@ -5181,6 +5251,7 @@
     hasPublishedDocs: true,
   );
 
+  ///  No parameters.
   static const CompileTimeErrorCode
       VARIABLE_PATTERN_KEYWORD_IN_DECLARATION_CONTEXT = CompileTimeErrorCode(
     'VARIABLE_PATTERN_KEYWORD_IN_DECLARATION_CONTEXT',
@@ -5691,6 +5762,18 @@
     hasPublishedDocs: true,
   );
 
+  ///  Users should not assign values marked `@doNotStore`.
+  ///
+  ///  Parameters:
+  ///  0: the name of the field or variable
+  static const WarningCode ASSIGNMENT_OF_DO_NOT_STORE = WarningCode(
+    'ASSIGNMENT_OF_DO_NOT_STORE',
+    "'{0}' is marked 'doNotStore' and shouldn't be assigned to a field or "
+        "top-level variable.",
+    correctionMessage: "Try removing the assignment.",
+    hasPublishedDocs: true,
+  );
+
   ///  Parameters:
   ///  0: the return type as derived by the type of the [Future].
   static const WarningCode BODY_MIGHT_COMPLETE_NORMALLY_CATCH_ERROR =
@@ -5745,8 +5828,49 @@
         "Try a constant of the same type as the matched value type.",
   );
 
-  ///  This is the new replacement for [HintCode.DEAD_CODE].
-  static const HintCode DEAD_CODE = HintCode.DEAD_CODE;
+  ///  Dead code is code that is never reached, this can happen for instance if a
+  ///  statement follows a return statement.
+  ///
+  ///  No parameters.
+  static const WarningCode DEAD_CODE = WarningCode(
+    'DEAD_CODE',
+    "Dead code.",
+    correctionMessage:
+        "Try removing the code, or fixing the code before it so that it can be "
+        "reached.",
+    hasPublishedDocs: true,
+  );
+
+  ///  Dead code is code that is never reached. This case covers cases where the
+  ///  user has catch clauses after `catch (e)` or `on Object catch (e)`.
+  ///
+  ///  No parameters.
+  static const WarningCode DEAD_CODE_CATCH_FOLLOWING_CATCH = WarningCode(
+    'DEAD_CODE_CATCH_FOLLOWING_CATCH',
+    "Dead code: Catch clauses after a 'catch (e)' or an 'on Object catch (e)' "
+        "are never reached.",
+    correctionMessage:
+        "Try reordering the catch clauses so that they can be reached, or "
+        "removing the unreachable catch clauses.",
+    hasPublishedDocs: true,
+  );
+
+  ///  Dead code is code that is never reached. This case covers cases where the
+  ///  user has an on-catch clause such as `on A catch (e)`, where a supertype of
+  ///  `A` was already caught.
+  ///
+  ///  Parameters:
+  ///  0: name of the subtype
+  ///  1: name of the supertype
+  static const WarningCode DEAD_CODE_ON_CATCH_SUBTYPE = WarningCode(
+    'DEAD_CODE_ON_CATCH_SUBTYPE',
+    "Dead code: This on-catch block won't be executed because '{0}' is a "
+        "subtype of '{1}' and hence will have been caught already.",
+    correctionMessage:
+        "Try reordering the catch clauses so that this block can be reached, "
+        "or removing the unreachable catch clause.",
+    hasPublishedDocs: true,
+  );
 
   ///  No parameters.
   static const WarningCode DEPRECATED_EXTENDS_FUNCTION = WarningCode(
@@ -5966,8 +6090,8 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere a @factory annotation is associated with
-  ///  anything other than a method.
+  ///  This warning is generated anywhere a @factory annotation is associated
+  ///  with anything other than a method.
   static const WarningCode INVALID_FACTORY_ANNOTATION = WarningCode(
     'INVALID_FACTORY_ANNOTATION',
     "Only methods can be annotated as factories.",
@@ -5989,8 +6113,8 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere an @immutable annotation is associated with
-  ///  anything other than a class.
+  ///  This warning is generated anywhere an @immutable annotation is associated
+  ///  with anything other than a class.
   static const WarningCode INVALID_IMMUTABLE_ANNOTATION = WarningCode(
     'INVALID_IMMUTABLE_ANNOTATION',
     "Only classes can be annotated as being immutable.",
@@ -6125,7 +6249,7 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere where `@nonVirtual` annotates something
+  ///  This warning is generated anywhere where `@nonVirtual` annotates something
   ///  other than a non-abstract instance member in a class or mixin.
   ///
   ///  No Parameters.
@@ -6137,7 +6261,7 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere where an instance member annotated with
+  ///  This warning is generated anywhere where an instance member annotated with
   ///  `@nonVirtual` is overridden in a subclass.
   ///
   ///  Parameters:
@@ -6150,7 +6274,7 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere where `@required` annotates a named
+  ///  This warning is generated anywhere where `@required` annotates a named
   ///  parameter with a default value.
   ///
   ///  Parameters:
@@ -6162,7 +6286,7 @@
     correctionMessage: "Remove @required.",
   );
 
-  ///  This hint is generated anywhere where `@required` annotates an optional
+  ///  This warning is generated anywhere where `@required` annotates an optional
   ///  positional parameter.
   ///
   ///  Parameters:
@@ -6175,8 +6299,8 @@
     correctionMessage: "Remove @required.",
   );
 
-  ///  This hint is generated anywhere where `@required` annotates a non optional
-  ///  positional parameter.
+  ///  This warning is generated anywhere where `@required` annotates a
+  ///  non-optional positional parameter.
   ///
   ///  Parameters:
   ///  0: the name of the member
@@ -6187,8 +6311,8 @@
     correctionMessage: "Remove @required.",
   );
 
-  ///  This hint is generated anywhere where `@sealed` annotates something other
-  ///  than a class.
+  ///  This warning is generated anywhere where `@sealed` annotates something
+  ///  other than a class.
   ///
   ///  No parameters.
   static const WarningCode INVALID_SEALED_ANNOTATION = WarningCode(
@@ -6206,8 +6330,8 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere where a member annotated with `@protected`
-  ///  is used outside of an instance member of a subclass.
+  ///  This warning is generated anywhere where a member annotated with
+  ///  `@protected` is used outside of an instance member of a subclass.
   ///
   ///  Parameters:
   ///  0: the name of the member
@@ -6227,7 +6351,7 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere where a member annotated with
+  ///  This warning is generated anywhere where a member annotated with
   ///  `@visibleForTemplate` is used outside of a "template" Dart file.
   ///
   ///  Parameters:
@@ -6239,7 +6363,7 @@
     "The member '{0}' can only be used within '{1}' or a template library.",
   );
 
-  ///  This hint is generated anywhere where a member annotated with
+  ///  This warning is generated anywhere where a member annotated with
   ///  `@visibleForTesting` is used outside the defining library, or a test.
   ///
   ///  Parameters:
@@ -6252,8 +6376,8 @@
     hasPublishedDocs: true,
   );
 
-  ///  This hint is generated anywhere where a private declaration is annotated
-  ///  with `@visibleForTemplate` or `@visibleForTesting`.
+  ///  This warning is generated anywhere where a private declaration is
+  ///  annotated with `@visibleForTemplate` or `@visibleForTesting`.
   ///
   ///  Parameters:
   ///  0: the name of the member
@@ -6310,8 +6434,8 @@
     uniqueName: 'MISSING_OVERRIDE_OF_MUST_BE_OVERRIDDEN_TWO',
   );
 
-  ///  Generate a hint for a constructor, function or method invocation where a
-  ///  required parameter is missing.
+  ///  Generates a warning for a constructor, function or method invocation where
+  ///  a required parameter is missing.
   ///
   ///  Parameters:
   ///  0: the name of the parameter
@@ -6321,8 +6445,8 @@
     hasPublishedDocs: true,
   );
 
-  ///  Generate a hint for a constructor, function or method invocation where a
-  ///  required parameter is missing.
+  ///  Generates a warning for a constructor, function or method invocation where
+  ///  a required parameter is missing.
   ///
   ///  Parameters:
   ///  0: the name of the parameter
@@ -6345,7 +6469,23 @@
     hasPublishedDocs: true,
   );
 
-  ///  Generate a hint for classes that inherit from classes annotated with
+  ///  This warning is generated anywhere where a `@sealed` class is used as a
+  ///  a superclass constraint of a mixin.
+  ///
+  ///  Parameters:
+  ///  0: the name of the sealed class
+  static const WarningCode MIXIN_ON_SEALED_CLASS = WarningCode(
+    'MIXIN_ON_SEALED_CLASS',
+    "The class '{0}' shouldn't be used as a mixin constraint because it is "
+        "sealed, and any class mixing in this mixin must have '{0}' as a "
+        "superclass.",
+    correctionMessage:
+        "Try composing with this class, or refer to its documentation for more "
+        "information.",
+    hasPublishedDocs: true,
+  );
+
+  ///  Generates a warning for classes that inherit from classes annotated with
   ///  `@immutable` but that are not immutable.
   ///
   ///  Parameters:
@@ -6367,9 +6507,33 @@
     hasPublishedDocs: true,
   );
 
-  ///  This is the new replacement for [HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR].
-  static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR =
-      HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
+  ///  Generates a warning for non-const instance creation using a constructor
+  ///  annotated with `@literal`.
+  ///
+  ///  Parameters:
+  ///  0: the name of the class defining the annotated constructor
+  static const WarningCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR = WarningCode(
+    'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
+    "This instance creation must be 'const', because the {0} constructor is "
+        "marked as '@literal'.",
+    correctionMessage: "Try adding a 'const' keyword.",
+    hasPublishedDocs: true,
+  );
+
+  ///  Generate a warning for non-const instance creation (with the `new` keyword)
+  ///  using a constructor annotated with `@literal`.
+  ///
+  ///  Parameters:
+  ///  0: the name of the class defining the annotated constructor
+  static const WarningCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW =
+      WarningCode(
+    'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
+    "This instance creation must be 'const', because the {0} constructor is "
+        "marked as '@literal'.",
+    correctionMessage: "Try replacing the 'new' keyword with 'const'.",
+    hasPublishedDocs: true,
+    uniqueName: 'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW',
+  );
 
   ///  No parameters.
   static const WarningCode NULLABLE_TYPE_IN_CATCH_CLAUSE = WarningCode(
@@ -6417,9 +6581,65 @@
         "an operand of a logical operator.",
   );
 
-  ///  This is the new replacement for [HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD].
-  static const HintCode OVERRIDE_ON_NON_OVERRIDING_FIELD =
-      HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD;
+  ///  No parameters.
+  static const WarningCode NULL_CHECK_ALWAYS_FAILS = WarningCode(
+    'NULL_CHECK_ALWAYS_FAILS',
+    "This null-check will always throw an exception because the expression "
+        "will always evaluate to 'null'.",
+    hasPublishedDocs: true,
+  );
+
+  ///  A field with the override annotation does not override a getter or setter.
+  ///
+  ///  No parameters.
+  static const WarningCode OVERRIDE_ON_NON_OVERRIDING_FIELD = WarningCode(
+    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+    "The field doesn't override an inherited getter or setter.",
+    correctionMessage:
+        "Try updating this class to match the superclass, or removing the "
+        "override annotation.",
+    hasPublishedDocs: true,
+    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_FIELD',
+  );
+
+  ///  A getter with the override annotation does not override an existing getter.
+  ///
+  ///  No parameters.
+  static const WarningCode OVERRIDE_ON_NON_OVERRIDING_GETTER = WarningCode(
+    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+    "The getter doesn't override an inherited getter.",
+    correctionMessage:
+        "Try updating this class to match the superclass, or removing the "
+        "override annotation.",
+    hasPublishedDocs: true,
+    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_GETTER',
+  );
+
+  ///  A method with the override annotation does not override an existing method.
+  ///
+  ///  No parameters.
+  static const WarningCode OVERRIDE_ON_NON_OVERRIDING_METHOD = WarningCode(
+    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+    "The method doesn't override an inherited method.",
+    correctionMessage:
+        "Try updating this class to match the superclass, or removing the "
+        "override annotation.",
+    hasPublishedDocs: true,
+    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_METHOD',
+  );
+
+  ///  A setter with the override annotation does not override an existing setter.
+  ///
+  ///  No parameters.
+  static const WarningCode OVERRIDE_ON_NON_OVERRIDING_SETTER = WarningCode(
+    'OVERRIDE_ON_NON_OVERRIDING_MEMBER',
+    "The setter doesn't override an inherited setter.",
+    correctionMessage:
+        "Try updating this class to match the superclass, or removing the "
+        "override annotation.",
+    hasPublishedDocs: true,
+    uniqueName: 'OVERRIDE_ON_NON_OVERRIDING_SETTER',
+  );
 
   ///  It is not an error to call or tear-off a method, setter, or getter, or to
   ///  read or write a field, on a receiver of static type `Never`.
@@ -6440,6 +6660,14 @@
         "Try checking for throw expressions or type errors in the receiver",
   );
 
+  static const WarningCode RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA =
+      WarningCode(
+    'RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA',
+    "A record literal with exactly one positional field requires a trailing "
+        "comma.",
+    correctionMessage: "Try adding a trailing comma.",
+  );
+
   ///  An error code indicating use of a removed lint rule.
   ///
   ///  Parameters:
@@ -6605,6 +6833,16 @@
     hasPublishedDocs: true,
   );
 
+  ///  Parameters:
+  ///  0: the version specified in the `@Since()` annotation
+  ///  1: the SDK version constraints
+  static const WarningCode SDK_VERSION_SINCE = WarningCode(
+    'SDK_VERSION_SINCE',
+    "This API is available since SDK {0}, but constraints '{1}' don't "
+        "guarantee it.",
+    correctionMessage: "Try updating the SDK constraints.",
+  );
+
   ///  No parameters.
   static const WarningCode SDK_VERSION_UI_AS_CODE = WarningCode(
     'SDK_VERSION_UI_AS_CODE',
@@ -6625,6 +6863,31 @@
     hasPublishedDocs: true,
   );
 
+  ///  When "strict-raw-types" is enabled, "raw types" must have type arguments.
+  ///
+  ///  A "raw type" is a type name that does not use inference to fill in missing
+  ///  type arguments; instead, each type argument is instantiated to its bound.
+  ///
+  ///  Parameters:
+  ///  0: the name of the generic type
+  static const WarningCode STRICT_RAW_TYPE = WarningCode(
+    'STRICT_RAW_TYPE',
+    "The generic type '{0}' should have explicit type arguments but doesn't.",
+    correctionMessage: "Use explicit type arguments for '{0}'.",
+  );
+
+  ///  Parameters:
+  ///  0: the name of the sealed class
+  static const WarningCode SUBTYPE_OF_SEALED_CLASS = WarningCode(
+    'SUBTYPE_OF_SEALED_CLASS',
+    "The class '{0}' shouldn't be extended, mixed in, or implemented because "
+        "it's sealed.",
+    correctionMessage:
+        "Try composing instead of inheriting, or refer to the documentation of "
+        "'{0}' for more information.",
+    hasPublishedDocs: true,
+  );
+
   ///  Parameters:
   ///  0: the unicode sequence of the code point.
   static const WarningCode TEXT_DIRECTION_CODE_POINT_IN_COMMENT = WarningCode(
@@ -6678,6 +6941,15 @@
   );
 
   ///  Parameters:
+  ///  0: the name of the undefined parameter
+  ///  1: the name of the targeted member
+  static const WarningCode UNDEFINED_REFERENCED_PARAMETER = WarningCode(
+    'UNDEFINED_REFERENCED_PARAMETER',
+    "The parameter '{0}' isn't defined by '{1}'.",
+    hasPublishedDocs: true,
+  );
+
+  ///  Parameters:
   ///  0: the name of the library being imported
   ///  1: the name in the show clause that isn't defined in the library
   static const WarningCode UNDEFINED_SHOWN_NAME = WarningCode(
@@ -6700,13 +6972,84 @@
   ///  This is the new replacement for [HintCode.UNNECESSARY_FINAL].
   static const HintCode UNNECESSARY_FINAL = HintCode.UNNECESSARY_FINAL;
 
-  ///  This is the new replacement for [HintCode.UNNECESSARY_TYPE_CHECK_FALSE].
-  static const HintCode UNNECESSARY_TYPE_CHECK_FALSE =
-      HintCode.UNNECESSARY_TYPE_CHECK_FALSE;
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_NAN_COMPARISON_FALSE = WarningCode(
+    'UNNECESSARY_NAN_COMPARISON',
+    "A double can't equal 'double.nan', so the condition is always 'false'.",
+    correctionMessage: "Try using 'double.isNan', or removing the condition.",
+    uniqueName: 'UNNECESSARY_NAN_COMPARISON_FALSE',
+  );
 
-  ///  This is the new replacement for [HintCode.UNNECESSARY_TYPE_CHECK_TRUE].
-  static const HintCode UNNECESSARY_TYPE_CHECK_TRUE =
-      HintCode.UNNECESSARY_TYPE_CHECK_TRUE;
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_NAN_COMPARISON_TRUE = WarningCode(
+    'UNNECESSARY_NAN_COMPARISON',
+    "A double can't equal 'double.nan', so the condition is always 'true'.",
+    correctionMessage: "Try using 'double.isNan', or removing the condition.",
+    uniqueName: 'UNNECESSARY_NAN_COMPARISON_TRUE',
+  );
+
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_NO_SUCH_METHOD = WarningCode(
+    'UNNECESSARY_NO_SUCH_METHOD',
+    "Unnecessary 'noSuchMethod' declaration.",
+    correctionMessage: "Try removing the declaration of 'noSuchMethod'.",
+    hasPublishedDocs: true,
+  );
+
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_NULL_COMPARISON_FALSE = WarningCode(
+    'UNNECESSARY_NULL_COMPARISON',
+    "The operand can't be null, so the condition is always 'false'.",
+    correctionMessage:
+        "Try removing the condition, an enclosing condition, or the whole "
+        "conditional statement.",
+    hasPublishedDocs: true,
+    uniqueName: 'UNNECESSARY_NULL_COMPARISON_FALSE',
+  );
+
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_NULL_COMPARISON_TRUE = WarningCode(
+    'UNNECESSARY_NULL_COMPARISON',
+    "The operand can't be null, so the condition is always 'true'.",
+    correctionMessage: "Remove the condition.",
+    hasPublishedDocs: true,
+    uniqueName: 'UNNECESSARY_NULL_COMPARISON_TRUE',
+  );
+
+  ///  Parameters:
+  ///  0: the name of the type
+  static const WarningCode UNNECESSARY_QUESTION_MARK = WarningCode(
+    'UNNECESSARY_QUESTION_MARK',
+    "The '?' is unnecessary because '{0}' is nullable without it.",
+    hasPublishedDocs: true,
+  );
+
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_SET_LITERAL = WarningCode(
+    'UNNECESSARY_SET_LITERAL',
+    "Braces unnecessarily wrap this expression in a set literal.",
+    correctionMessage: "Try removing the set literal around the expression.",
+  );
+
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_TYPE_CHECK_FALSE = WarningCode(
+    'UNNECESSARY_TYPE_CHECK',
+    "Unnecessary type check; the result is always 'false'.",
+    correctionMessage:
+        "Try correcting the type check, or removing the type check.",
+    hasPublishedDocs: true,
+    uniqueName: 'UNNECESSARY_TYPE_CHECK_FALSE',
+  );
+
+  ///  No parameters.
+  static const WarningCode UNNECESSARY_TYPE_CHECK_TRUE = WarningCode(
+    'UNNECESSARY_TYPE_CHECK',
+    "Unnecessary type check; the result is always 'true'.",
+    correctionMessage:
+        "Try correcting the type check, or removing the type check.",
+    hasPublishedDocs: true,
+    uniqueName: 'UNNECESSARY_TYPE_CHECK_TRUE',
+  );
 
   ///  No parameters.
   static const WarningCode UNNECESSARY_WILDCARD_PATTERN = WarningCode(
@@ -6715,6 +7058,10 @@
     correctionMessage: "Try removing the wildcard pattern.",
   );
 
+  ///  This is the new replacement for [HintCode.UNREACHABLE_SWITCH_CASE].
+  static const HintCode UNREACHABLE_SWITCH_CASE =
+      HintCode.UNREACHABLE_SWITCH_CASE;
+
   ///  Parameters:
   ///  0: the name of the exception variable
   static const WarningCode UNUSED_CATCH_CLAUSE = WarningCode(
@@ -6747,9 +7094,48 @@
   ///  This is the new replacement for [HintCode.UNUSED_IMPORT].
   static const HintCode UNUSED_IMPORT = HintCode.UNUSED_IMPORT;
 
+  ///  Parameters:
+  ///  0: the label that isn't used
+  static const WarningCode UNUSED_LABEL = WarningCode(
+    'UNUSED_LABEL',
+    "The label '{0}' isn't used.",
+    correctionMessage:
+        "Try removing the label, or using it in either a 'break' or 'continue' "
+        "statement.",
+    hasPublishedDocs: true,
+  );
+
   ///  This is the new replacement for [HintCode.UNUSED_LOCAL_VARIABLE].
   static const HintCode UNUSED_LOCAL_VARIABLE = HintCode.UNUSED_LOCAL_VARIABLE;
 
+  ///  Parameters:
+  ///  0: the name of the annotated method, property or function
+  static const WarningCode UNUSED_RESULT = WarningCode(
+    'UNUSED_RESULT',
+    "The value of '{0}' should be used.",
+    correctionMessage:
+        "Try using the result by invoking a member, passing it to a function, "
+        "or returning it from this function.",
+    hasPublishedDocs: true,
+  );
+
+  ///  The result of invoking a method, property, or function annotated with
+  ///  `@useResult` must be used (assigned, passed to a function as an argument,
+  ///  or returned by a function).
+  ///
+  ///  Parameters:
+  ///  0: the name of the annotated method, property or function
+  ///  1: message details
+  static const WarningCode UNUSED_RESULT_WITH_MESSAGE = WarningCode(
+    'UNUSED_RESULT',
+    "'{0}' should be used. {1}.",
+    correctionMessage:
+        "Try using the result by invoking a member, passing it to a function, "
+        "or returning it from this function.",
+    hasPublishedDocs: true,
+    uniqueName: 'UNUSED_RESULT_WITH_MESSAGE',
+  );
+
   /// Initialize a newly created error code to have the given [name].
   const WarningCode(
     String name,
diff --git a/analyzer/lib/src/error/dead_code_verifier.dart b/analyzer/lib/src/error/dead_code_verifier.dart
index 516ab84..70975a7 100644
--- a/analyzer/lib/src/error/dead_code_verifier.dart
+++ b/analyzer/lib/src/error/dead_code_verifier.dart
@@ -102,21 +102,21 @@
     Namespace namespace =
         NamespaceBuilder().createExportNamespaceForLibrary(library);
     NodeList<SimpleIdentifier> names;
-    ErrorCode hintCode;
+    ErrorCode warningCode;
     if (combinator is HideCombinator) {
       names = combinator.hiddenNames;
-      hintCode = WarningCode.UNDEFINED_HIDDEN_NAME;
+      warningCode = WarningCode.UNDEFINED_HIDDEN_NAME;
     } else {
       names = (combinator as ShowCombinator).shownNames;
-      hintCode = WarningCode.UNDEFINED_SHOWN_NAME;
+      warningCode = WarningCode.UNDEFINED_SHOWN_NAME;
     }
     for (SimpleIdentifier name in names) {
       String nameStr = name.name;
       Element? element = namespace.get(nameStr);
       element ??= namespace.get("$nameStr=");
       if (element == null) {
-        _errorReporter
-            .reportErrorForNode(hintCode, name, [library.identifier, nameStr]);
+        _errorReporter.reportErrorForNode(
+            warningCode, name, [library.identifier, nameStr]);
       }
     }
   }
@@ -129,7 +129,7 @@
     } finally {
       for (Label label in labelTracker.unusedLabels()) {
         _errorReporter.reportErrorForNode(
-            HintCode.UNUSED_LABEL, label, [label.label.name]);
+            WarningCode.UNUSED_LABEL, label, [label.label.name]);
       }
       _labelTracker = labelTracker.outerTracker;
     }
@@ -168,7 +168,7 @@
             var offset = node.operator.offset;
             var length = node.rightOperand.end - offset;
             _errorReporter.reportErrorForOffset(
-                HintCode.DEAD_CODE, offset, length);
+                WarningCode.DEAD_CODE, offset, length);
             // Only visit the LHS:
             lhsCondition.accept(this);
             return;
@@ -217,13 +217,13 @@
         if (result.value?.toBoolValue() == true) {
           // Report error on "else" block: true ? 1 : !2!
           _errorReporter.reportErrorForNode(
-              HintCode.DEAD_CODE, node.elseExpression);
+              WarningCode.DEAD_CODE, node.elseExpression);
           node.thenExpression.accept(this);
           return;
         } else {
           // Report error on "if" block: false ? !1! : 2
           _errorReporter.reportErrorForNode(
-              HintCode.DEAD_CODE, node.thenExpression);
+              WarningCode.DEAD_CODE, node.thenExpression);
           node.elseExpression.accept(this);
           return;
         }
@@ -243,14 +243,15 @@
           // Report error on else block: if(true) {} else {!}
           var elseElement = node.elseElement;
           if (elseElement != null) {
-            _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, elseElement);
+            _errorReporter.reportErrorForNode(
+                WarningCode.DEAD_CODE, elseElement);
             node.thenElement.accept(this);
             return;
           }
         } else {
           // Report error on if block: if (false) {!} else {}
           _errorReporter.reportErrorForNode(
-              HintCode.DEAD_CODE, node.thenElement);
+              WarningCode.DEAD_CODE, node.thenElement);
           node.elseElement?.accept(this);
           return;
         }
@@ -271,14 +272,14 @@
           var elseStatement = node.elseStatement;
           if (elseStatement != null) {
             _errorReporter.reportErrorForNode(
-                HintCode.DEAD_CODE, elseStatement);
+                WarningCode.DEAD_CODE, elseStatement);
             node.thenStatement.accept(this);
             return;
           }
         } else {
           // Report error on if block: if (false) {!} else {}
           _errorReporter.reportErrorForNode(
-              HintCode.DEAD_CODE, node.thenStatement);
+              WarningCode.DEAD_CODE, node.thenStatement);
           node.elseStatement?.accept(this);
           return;
         }
@@ -336,7 +337,7 @@
       if (result != null) {
         if (result.value?.toBoolValue() == false) {
           // Report error on while block: while (false) {!}
-          _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body);
+          _errorReporter.reportErrorForNode(WarningCode.DEAD_CODE, node.body);
           return;
         }
       }
@@ -378,7 +379,8 @@
         }
         int offset = nextStatement.offset;
         int length = lastStatement.end - offset;
-        _errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length);
+        _errorReporter.reportErrorForOffset(
+            WarningCode.DEAD_CODE, offset, length);
         return;
       }
     }
@@ -489,7 +491,7 @@
       }
 
       if (node is SwitchMember && node == firstDeadNode) {
-        _errorReporter.reportErrorForToken(HintCode.DEAD_CODE, node.keyword);
+        _errorReporter.reportErrorForToken(WarningCode.DEAD_CODE, node.keyword);
         _firstDeadNode = null;
         return;
       }
@@ -537,9 +539,9 @@
             whileOffset = body.rightBracket.offset;
           }
           _errorReporter.reportErrorForOffset(
-              HintCode.DEAD_CODE, doOffset, doEnd - doOffset);
+              WarningCode.DEAD_CODE, doOffset, doEnd - doOffset);
           _errorReporter.reportErrorForOffset(
-              HintCode.DEAD_CODE, whileOffset, whileEnd - whileOffset);
+              WarningCode.DEAD_CODE, whileOffset, whileEnd - whileOffset);
           offset = parent.semicolon.next!.offset;
           if (parent.hasBreakStatement) {
             offset = node.end;
@@ -560,7 +562,7 @@
         var length = node.end - offset;
         if (length > 0) {
           _errorReporter.reportErrorForOffset(
-              HintCode.DEAD_CODE, offset, length);
+              WarningCode.DEAD_CODE, offset, length);
         }
       }
 
@@ -664,7 +666,7 @@
       var beginToken = updaters.beginToken;
       var endToken = updaters.endToken;
       if (beginToken != null && endToken != null) {
-        _errorReporter.reportErrorForOffset(HintCode.DEAD_CODE,
+        _errorReporter.reportErrorForOffset(WarningCode.DEAD_CODE,
             beginToken.offset, endToken.end - beginToken.offset);
       }
     }
@@ -697,7 +699,7 @@
           node = parent!;
           parent = node.parent;
         }
-        _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node);
+        _errorReporter.reportErrorForNode(WarningCode.DEAD_CODE, node);
       }
     }
   }
@@ -743,7 +745,7 @@
         _errorReporter(
           catchClauses[index + 1],
           catchClauses.last,
-          HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
+          WarningCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
           const [],
         );
         _done = true;
@@ -758,7 +760,7 @@
         _errorReporter(
           catchClause,
           catchClauses.last,
-          HintCode.DEAD_CODE_ON_CATCH_SUBTYPE,
+          WarningCode.DEAD_CODE_ON_CATCH_SUBTYPE,
           [currentType, type],
         );
         _done = true;
diff --git a/analyzer/lib/src/error/error_code_values.g.dart b/analyzer/lib/src/error/error_code_values.g.dart
index 848ef24..1266984 100644
--- a/analyzer/lib/src/error/error_code_values.g.dart
+++ b/analyzer/lib/src/error/error_code_values.g.dart
@@ -235,7 +235,6 @@
   CompileTimeErrorCode.INCONSISTENT_INHERITANCE_GETTER_AND_METHOD,
   CompileTimeErrorCode.INCONSISTENT_LANGUAGE_VERSION_OVERRIDE,
   CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_LOGICAL_OR,
-  CompileTimeErrorCode.INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE,
   CompileTimeErrorCode.INITIALIZER_FOR_NON_EXISTENT_FIELD,
   CompileTimeErrorCode.INITIALIZER_FOR_STATIC_FIELD,
   CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD,
@@ -315,6 +314,7 @@
   CompileTimeErrorCode.MIXIN_APPLICATION_NOT_IMPLEMENTED_INTERFACE,
   CompileTimeErrorCode.MIXIN_APPLICATION_NO_CONCRETE_SUPER_INVOKED_MEMBER,
   CompileTimeErrorCode.MIXIN_APPLICATION_NO_CONCRETE_SUPER_INVOKED_SETTER,
+  CompileTimeErrorCode.MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT,
   CompileTimeErrorCode.MIXIN_CLASS_DECLARES_CONSTRUCTOR,
   CompileTimeErrorCode.MIXIN_DEFERRED_CLASS,
   CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT,
@@ -402,6 +402,10 @@
   CompileTimeErrorCode.PATTERN_TYPE_MISMATCH_IN_IRREFUTABLE_CONTEXT,
   CompileTimeErrorCode.PATTERN_VARIABLE_ASSIGNMENT_INSIDE_GUARD,
   CompileTimeErrorCode
+      .PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE,
+  CompileTimeErrorCode.PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL,
+  CompileTimeErrorCode.PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES,
+  CompileTimeErrorCode
       .POSITIONAL_SUPER_FORMAL_PARAMETER_WITH_POSITIONAL_ARGUMENT,
   CompileTimeErrorCode.PREFIX_COLLIDES_WITH_TOP_LEVEL_MEMBER,
   CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT,
@@ -429,6 +433,7 @@
   CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
   CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION,
   CompileTimeErrorCode.REFUTABLE_PATTERN_IN_IRREFUTABLE_CONTEXT,
+  CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE,
   CompileTimeErrorCode
       .RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL,
   CompileTimeErrorCode.REST_ELEMENT_NOT_LAST_IN_MAP_PATTERN,
@@ -448,6 +453,8 @@
   CompileTimeErrorCode.SHARED_DEFERRED_PREFIX,
   CompileTimeErrorCode.SPREAD_EXPRESSION_FROM_DEFERRED_LIBRARY,
   CompileTimeErrorCode.STATIC_ACCESS_TO_INSTANCE_MEMBER,
+  CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
+  CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,
   CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_TYPE_IS_NOT_SUBTYPE_OF_ASSOCIATED,
   CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_NAMED,
   CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_POSITIONAL,
@@ -576,11 +583,7 @@
   FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS,
   FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS,
   FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH,
-  HintCode.ASSIGNMENT_OF_DO_NOT_STORE,
   HintCode.CAN_BE_NULL_AFTER_NULL_AWARE,
-  HintCode.DEAD_CODE,
-  HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
-  HintCode.DEAD_CODE_ON_CATCH_SUBTYPE,
   HintCode.DEPRECATED_COLON_FOR_DEFAULT_VALUE,
   HintCode.DEPRECATED_EXPORT_USE,
   HintCode.DEPRECATED_MEMBER_USE,
@@ -588,44 +591,19 @@
   HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE_WITH_MESSAGE,
   HintCode.DEPRECATED_MEMBER_USE_WITH_MESSAGE,
   HintCode.DIVISION_OPTIMIZATION,
-  HintCode.FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE,
-  HintCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE,
   HintCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION,
   HintCode.IMPORT_OF_LEGACY_LIBRARY_INTO_NULL_SAFE,
-  HintCode.MIXIN_ON_SEALED_CLASS,
-  HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR,
-  HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW,
-  HintCode.NULL_CHECK_ALWAYS_FAILS,
-  HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
-  HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
-  HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
-  HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
-  HintCode.STRICT_RAW_TYPE,
-  HintCode.SUBTYPE_OF_SEALED_CLASS,
-  HintCode.UNDEFINED_REFERENCED_PARAMETER,
   HintCode.UNIGNORABLE_IGNORE,
   HintCode.UNNECESSARY_CAST,
   HintCode.UNNECESSARY_FINAL,
   HintCode.UNNECESSARY_IGNORE,
   HintCode.UNNECESSARY_IMPORT,
-  HintCode.UNNECESSARY_NAN_COMPARISON_FALSE,
-  HintCode.UNNECESSARY_NAN_COMPARISON_TRUE,
-  HintCode.UNNECESSARY_NO_SUCH_METHOD,
-  HintCode.UNNECESSARY_NULL_COMPARISON_FALSE,
-  HintCode.UNNECESSARY_NULL_COMPARISON_TRUE,
-  HintCode.UNNECESSARY_QUESTION_MARK,
-  HintCode.UNNECESSARY_SET_LITERAL,
-  HintCode.UNNECESSARY_TYPE_CHECK_FALSE,
-  HintCode.UNNECESSARY_TYPE_CHECK_TRUE,
   HintCode.UNREACHABLE_SWITCH_CASE,
   HintCode.UNUSED_ELEMENT,
   HintCode.UNUSED_ELEMENT_PARAMETER,
   HintCode.UNUSED_FIELD,
   HintCode.UNUSED_IMPORT,
-  HintCode.UNUSED_LABEL,
   HintCode.UNUSED_LOCAL_VARIABLE,
-  HintCode.UNUSED_RESULT,
-  HintCode.UNUSED_RESULT_WITH_MESSAGE,
   HintCode.UNUSED_SHOWN_NAME,
   LanguageCode.IMPLICIT_DYNAMIC_FIELD,
   LanguageCode.IMPLICIT_DYNAMIC_FUNCTION,
@@ -741,6 +719,7 @@
   ParserErrorCode.FINAL_CONSTRUCTOR,
   ParserErrorCode.FINAL_ENUM,
   ParserErrorCode.FINAL_METHOD,
+  ParserErrorCode.FINAL_MIXIN_CLASS,
   ParserErrorCode.FINAL_TYPEDEF,
   ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR,
   ParserErrorCode.GETTER_CONSTRUCTOR,
@@ -752,6 +731,7 @@
   ParserErrorCode.IMPLEMENTS_BEFORE_WITH,
   ParserErrorCode.IMPORT_DIRECTIVE_AFTER_PART_DIRECTIVE,
   ParserErrorCode.INITIALIZED_VARIABLE_IN_FOR_EACH,
+  ParserErrorCode.INTERFACE_MIXIN_CLASS,
   ParserErrorCode.INVALID_AWAIT_IN_FOR,
   ParserErrorCode.INVALID_CODE_POINT,
   ParserErrorCode.INVALID_COMMENT_REFERENCE,
@@ -845,6 +825,7 @@
   ParserErrorCode.RECORD_TYPE_ONE_POSITIONAL_NO_TRAILING_COMMA,
   ParserErrorCode.REDIRECTING_CONSTRUCTOR_WITH_BODY,
   ParserErrorCode.REDIRECTION_IN_NON_FACTORY_CONSTRUCTOR,
+  ParserErrorCode.SEALED_MIXIN_CLASS,
   ParserErrorCode.SETTER_CONSTRUCTOR,
   ParserErrorCode.SETTER_IN_FUNCTION,
   ParserErrorCode.STACK_OVERFLOW,
@@ -911,11 +892,15 @@
   TodoCode.TODO,
   TodoCode.UNDONE,
   WarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE_TO_ERROR_HANDLER,
+  WarningCode.ASSIGNMENT_OF_DO_NOT_STORE,
   WarningCode.BODY_MIGHT_COMPLETE_NORMALLY_CATCH_ERROR,
   WarningCode.BODY_MIGHT_COMPLETE_NORMALLY_NULLABLE,
   WarningCode.CAST_FROM_NULLABLE_ALWAYS_FAILS,
   WarningCode.CAST_FROM_NULL_ALWAYS_FAILS,
   WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE,
+  WarningCode.DEAD_CODE,
+  WarningCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
+  WarningCode.DEAD_CODE_ON_CATCH_SUBTYPE,
   WarningCode.DEPRECATED_EXTENDS_FUNCTION,
   WarningCode.DEPRECATED_IMPLEMENTS_FUNCTION,
   WarningCode.DEPRECATED_MIXIN_FUNCTION,
@@ -971,14 +956,23 @@
   WarningCode.MISSING_REQUIRED_PARAM,
   WarningCode.MISSING_REQUIRED_PARAM_WITH_DETAILS,
   WarningCode.MISSING_RETURN,
+  WarningCode.MIXIN_ON_SEALED_CLASS,
   WarningCode.MUST_BE_IMMUTABLE,
   WarningCode.MUST_CALL_SUPER,
+  WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR,
+  WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW,
   WarningCode.NULLABLE_TYPE_IN_CATCH_CLAUSE,
   WarningCode.NULL_ARGUMENT_TO_NON_NULL_TYPE,
   WarningCode.NULL_AWARE_BEFORE_OPERATOR,
   WarningCode.NULL_AWARE_IN_CONDITION,
   WarningCode.NULL_AWARE_IN_LOGICAL_OPERATOR,
+  WarningCode.NULL_CHECK_ALWAYS_FAILS,
+  WarningCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
+  WarningCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
+  WarningCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
+  WarningCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
   WarningCode.RECEIVER_OF_TYPE_NEVER,
+  WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
   WarningCode.REMOVED_LINT_USE,
   WarningCode.REPLACED_LINT_USE,
   WarningCode.RETURN_OF_DO_NOT_STORE,
@@ -994,16 +988,32 @@
   WarningCode.SDK_VERSION_IS_EXPRESSION_IN_CONST_CONTEXT,
   WarningCode.SDK_VERSION_NEVER,
   WarningCode.SDK_VERSION_SET_LITERAL,
+  WarningCode.SDK_VERSION_SINCE,
   WarningCode.SDK_VERSION_UI_AS_CODE,
   WarningCode.SDK_VERSION_UI_AS_CODE_IN_CONST_CONTEXT,
+  WarningCode.STRICT_RAW_TYPE,
+  WarningCode.SUBTYPE_OF_SEALED_CLASS,
   WarningCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT,
   WarningCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL,
   WarningCode.TYPE_CHECK_IS_NOT_NULL,
   WarningCode.TYPE_CHECK_IS_NULL,
   WarningCode.UNDEFINED_HIDDEN_NAME,
+  WarningCode.UNDEFINED_REFERENCED_PARAMETER,
   WarningCode.UNDEFINED_SHOWN_NAME,
   WarningCode.UNNECESSARY_CAST_PATTERN,
+  WarningCode.UNNECESSARY_NAN_COMPARISON_FALSE,
+  WarningCode.UNNECESSARY_NAN_COMPARISON_TRUE,
+  WarningCode.UNNECESSARY_NO_SUCH_METHOD,
+  WarningCode.UNNECESSARY_NULL_COMPARISON_FALSE,
+  WarningCode.UNNECESSARY_NULL_COMPARISON_TRUE,
+  WarningCode.UNNECESSARY_QUESTION_MARK,
+  WarningCode.UNNECESSARY_SET_LITERAL,
+  WarningCode.UNNECESSARY_TYPE_CHECK_FALSE,
+  WarningCode.UNNECESSARY_TYPE_CHECK_TRUE,
   WarningCode.UNNECESSARY_WILDCARD_PATTERN,
   WarningCode.UNUSED_CATCH_CLAUSE,
   WarningCode.UNUSED_CATCH_STACK,
+  WarningCode.UNUSED_LABEL,
+  WarningCode.UNUSED_RESULT,
+  WarningCode.UNUSED_RESULT_WITH_MESSAGE,
 ];
diff --git a/analyzer/lib/src/error/imports_verifier.dart b/analyzer/lib/src/error/imports_verifier.dart
index baca24c..9db227a 100644
--- a/analyzer/lib/src/error/imports_verifier.dart
+++ b/analyzer/lib/src/error/imports_verifier.dart
@@ -189,7 +189,7 @@
 /// otherwise a [HintCode.UNUSED_IMPORT] hint is generated with
 /// [generateUnusedImportHints].
 ///
-/// Additionally, [generateDuplicateImportHints] generates
+/// Additionally, [generateDuplicateImportWarnings] generates
 /// [HintCode.DUPLICATE_IMPORT] hints and [HintCode.UNUSED_SHOWN_NAME] hints.
 ///
 /// While this class does not yet have support for an "Organize Imports" action,
@@ -316,9 +316,9 @@
 
   /// Any time after the defining compilation unit has been visited by this
   /// visitor, this method can be called to report an
-  /// [StaticWarningCode.DUPLICATE_EXPORT] hint for each of the export
+  /// [WarningCode.DUPLICATE_EXPORT] hint for each of the export
   /// directives in the [_duplicateExports] list.
-  void generateDuplicateExportHints(ErrorReporter errorReporter) {
+  void generateDuplicateExportWarnings(ErrorReporter errorReporter) {
     var length = _duplicateExports.length;
     for (var i = 0; i < length; i++) {
       errorReporter.reportErrorForNode(
@@ -328,9 +328,9 @@
 
   /// Any time after the defining compilation unit has been visited by this
   /// visitor, this method can be called to report an
-  /// [StaticWarningCode.DUPLICATE_IMPORT] hint for each of the import
+  /// [WarningCode.DUPLICATE_IMPORT] hint for each of the import
   /// directives in the [_duplicateImports] list.
-  void generateDuplicateImportHints(ErrorReporter errorReporter) {
+  void generateDuplicateImportWarnings(ErrorReporter errorReporter) {
     var length = _duplicateImports.length;
     for (var i = 0; i < length; i++) {
       errorReporter.reportErrorForNode(
@@ -338,13 +338,13 @@
     }
   }
 
-  /// Report a [StaticWarningCode.DUPLICATE_SHOWN_NAME] and
-  /// [StaticWarningCode.DUPLICATE_HIDDEN_NAME] hints for each duplicate shown
-  /// or hidden name.
+  /// Report a [WarningCode.DUPLICATE_SHOWN_NAME] and
+  /// [WarningCode.DUPLICATE_HIDDEN_NAME] hints for each duplicate shown or
+  /// hidden name.
   ///
   /// Only call this method after all of the compilation units have been visited
   /// by this visitor.
-  void generateDuplicateShownHiddenNameHints(ErrorReporter reporter) {
+  void generateDuplicateShownHiddenNameWarnings(ErrorReporter reporter) {
     _duplicateHiddenNamesMap.forEach(
         (NamespaceDirective directive, List<SimpleIdentifier> identifiers) {
       int length = identifiers.length;
diff --git a/analyzer/lib/src/error/literal_element_verifier.dart b/analyzer/lib/src/error/literal_element_verifier.dart
index 4cb6aef..275d9fc 100644
--- a/analyzer/lib/src/error/literal_element_verifier.dart
+++ b/analyzer/lib/src/error/literal_element_verifier.dart
@@ -68,7 +68,7 @@
   void _verifyElement(CollectionElement? element) {
     if (element is Expression) {
       if (forList || forSet) {
-        if (!elementType!.isVoid &&
+        if (elementType is! VoidType &&
             _errorVerifier.checkForUseOfVoidResult(element)) {
           return;
         }
@@ -103,14 +103,14 @@
   /// Verify that the [entry]'s key and value are assignable to [mapKeyType]
   /// and [mapValueType].
   void _verifyMapLiteralEntry(MapLiteralEntry entry) {
-    var mapKeyType = this.mapKeyType;
-    if (!mapKeyType!.isVoid &&
+    var mapKeyType = this.mapKeyType!;
+    if (mapKeyType is! VoidType &&
         _errorVerifier.checkForUseOfVoidResult(entry.key)) {
       return;
     }
 
-    var mapValueType = this.mapValueType;
-    if (!mapValueType!.isVoid &&
+    var mapValueType = this.mapValueType!;
+    if (mapValueType is! VoidType &&
         _errorVerifier.checkForUseOfVoidResult(entry.value)) {
       return;
     }
diff --git a/analyzer/lib/src/error/override_verifier.dart b/analyzer/lib/src/error/override_verifier.dart
index dc2004a..59cfd9b 100644
--- a/analyzer/lib/src/error/override_verifier.dart
+++ b/analyzer/lib/src/error/override_verifier.dart
@@ -55,7 +55,7 @@
         if (setter != null && _isOverride(setter)) continue;
 
         _errorReporter.reportErrorForToken(
-          HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
+          WarningCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
           field.name,
         );
       }
@@ -68,18 +68,18 @@
     if (element.hasOverride && !_isOverride(element)) {
       if (element is MethodElement) {
         _errorReporter.reportErrorForToken(
-          HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
+          WarningCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
           node.name,
         );
       } else if (element is PropertyAccessorElement) {
         if (element.isGetter) {
           _errorReporter.reportErrorForToken(
-            HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
+            WarningCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
             node.name,
           );
         } else {
           _errorReporter.reportErrorForToken(
-            HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
+            WarningCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
             node.name,
           );
         }
diff --git a/analyzer/lib/src/error/return_type_verifier.dart b/analyzer/lib/src/error/return_type_verifier.dart
index f28c22f..b1d4c40 100644
--- a/analyzer/lib/src/error/return_type_verifier.dart
+++ b/analyzer/lib/src/error/return_type_verifier.dart
@@ -41,7 +41,7 @@
 
   void verifyExpressionFunctionBody(ExpressionFunctionBody node) {
     // This enables concise declarations of void functions.
-    if (_flattenedReturnType.isVoid) {
+    if (_flattenedReturnType is VoidType) {
       return;
     }
 
@@ -91,7 +91,7 @@
       // It is a compile-time error if the declared return type of
       // a function marked `sync*` or `async*` is `void`.
       if (enclosingExecutable.isGenerator) {
-        if (enclosingExecutable.returnType.isVoid) {
+        if (enclosingExecutable.returnType is VoidType) {
           return reportError();
         }
       }
@@ -202,7 +202,7 @@
     if (enclosingExecutable.isSynchronous) {
       // It is a compile-time error if `T` is `void`,
       // and `S` is neither `void`, `dynamic`, nor `Null`.
-      if (T.isVoid) {
+      if (T is VoidType) {
         if (!_isVoidDynamicOrNull(S)) {
           reportTypeError();
           return;
@@ -210,7 +210,7 @@
       }
       // It is a compile-time error if `S` is `void`,
       // and `T` is neither `void`, `dynamic`, nor `Null`.
-      if (S.isVoid) {
+      if (S is VoidType) {
         if (!_isVoidDynamicOrNull(T)) {
           reportTypeError();
           return;
@@ -218,7 +218,7 @@
       }
       // It is a compile-time error if `S` is not `void`,
       // and `S` is not assignable to `T`.
-      if (!S.isVoid) {
+      if (S is! VoidType) {
         if (!_typeSystem.isAssignableTo(S, T)) {
           reportTypeError();
           return;
@@ -238,7 +238,7 @@
       // implementing it now would be a breaking change. So, the code below
       // intentionally does not implement the specification.
       // https://github.com/dart-lang/sdk/issues/41803#issuecomment-635852474
-      if (T.isVoid) {
+      if (T is VoidType) {
         if (!_isVoidDynamicOrNull(flatten_S)) {
           reportTypeError();
           return;
@@ -246,7 +246,7 @@
       }
       // It is a compile-time error if `flatten(S)` is `void`,
       // and `flatten(T)` is neither `void`, `dynamic`, nor `Null`.
-      if (flatten_S.isVoid) {
+      if (flatten_S is VoidType) {
         if (!_isVoidDynamicOrNull(flatten_T)) {
           reportTypeError();
           return;
@@ -254,7 +254,7 @@
       }
       // It is a compile-time error if `flatten(S)` is not `void`,
       // and `Future<flatten(S)>` is not assignable to `T`.
-      if (!flatten_S.isVoid) {
+      if (flatten_S is! VoidType) {
         var future_flatten_S = _typeProvider.futureType(flatten_S);
         if (!_typeSystem.isAssignableTo(future_flatten_S, T)) {
           reportTypeError();
@@ -321,7 +321,7 @@
     if (enclosingExecutable.isSynchronous) {
       // It is a compile-time error if `T` is `void`,
       // and `S` is neither `void`, `dynamic`, nor `Null`.
-      if (T.isVoid) {
+      if (T is VoidType) {
         if (!_isVoidDynamicOrNull(S)) {
           reportTypeError();
           return;
@@ -329,7 +329,7 @@
       }
       // It is a compile-time error if `S` is `void`,
       // and `T` is neither `void` nor `dynamic`.
-      if (S.isVoid) {
+      if (S is VoidType) {
         if (!_isVoidDynamic(T)) {
           reportTypeError();
           return;
@@ -337,7 +337,21 @@
       }
       // It is a compile-time error if `S` is not `void`,
       // and `S` is not assignable to `T`.
-      if (!S.isVoid) {
+      if (S is! VoidType) {
+        if (T is RecordType) {
+          if (S is! RecordType && T.positionalFields.length == 1) {
+            var field = T.positionalFields.first;
+            if (_typeSystem.isAssignableTo(field.type, S)) {
+              _errorReporter.reportErrorForNode(
+                WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
+                expression,
+                [],
+              );
+              return;
+            }
+          }
+        }
+
         if (!_typeSystem.isAssignableTo(S, T)) {
           reportTypeError();
           return;
@@ -352,7 +366,7 @@
       var flatten_S = _typeSystem.flatten(S);
       // It is a compile-time error if `flatten(T)` is `void`,
       // and `flatten(S)` is neither `void`, `dynamic`, nor `Null`.
-      if (T_v.isVoid) {
+      if (T_v is VoidType) {
         if (!_isVoidDynamicOrNull(flatten_S)) {
           reportTypeError();
           return;
@@ -360,7 +374,7 @@
       }
       // It is a compile-time error if `flatten(S)` is `void`,
       // and `flatten(T)` is neither `void`, `dynamic`.
-      if (flatten_S.isVoid) {
+      if (flatten_S is VoidType) {
         if (!_isVoidDynamic(T_v)) {
           reportTypeError();
           return;
@@ -368,7 +382,7 @@
       }
       // It is a compile-time error if `flatten(S)` is not `void`,
       // and `Future<flatten(S)>` is not assignable to `T`.
-      if (!flatten_S.isVoid) {
+      if (flatten_S is! VoidType) {
         if (!_typeSystem.isAssignableTo(S, T_v) &&
             !_typeSystem.isSubtypeOf(flatten_S, T_v)) {
           reportTypeError();
@@ -434,10 +448,10 @@
   }
 
   static bool _isVoidDynamic(DartType type) {
-    return type.isVoid || type.isDynamic;
+    return type is VoidType || type.isDynamic;
   }
 
   static bool _isVoidDynamicOrNull(DartType type) {
-    return type.isVoid || type.isDynamic || type.isDartCoreNull;
+    return type is VoidType || type.isDynamic || type.isDartCoreNull;
   }
 }
diff --git a/analyzer/lib/src/error/type_arguments_verifier.dart b/analyzer/lib/src/error/type_arguments_verifier.dart
index ce1acff..3a1f861 100644
--- a/analyzer/lib/src/error/type_arguments_verifier.dart
+++ b/analyzer/lib/src/error/type_arguments_verifier.dart
@@ -308,7 +308,7 @@
         // See https://github.com/dart-lang/language/blob/master/resources/type-system/strict-raw-types.md#conditions-for-a-raw-type-hint
       } else {
         _errorReporter
-            .reportErrorForNode(HintCode.STRICT_RAW_TYPE, node, [type]);
+            .reportErrorForNode(WarningCode.STRICT_RAW_TYPE, node, [type]);
       }
     }
   }
diff --git a/analyzer/lib/src/error/unused_local_elements_verifier.dart b/analyzer/lib/src/error/unused_local_elements_verifier.dart
index dfc767e..39470b3 100644
--- a/analyzer/lib/src/error/unused_local_elements_verifier.dart
+++ b/analyzer/lib/src/error/unused_local_elements_verifier.dart
@@ -11,6 +11,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
@@ -499,9 +500,13 @@
   }
 
   @override
-  void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
+  void visitDeclaredVariablePattern(
+    covariant DeclaredVariablePatternImpl node,
+  ) {
     final declaredElement = node.declaredElement!;
-    _visitLocalVariableElement(declaredElement);
+    if (!declaredElement.isDuplicate) {
+      _visitLocalVariableElement(declaredElement);
+    }
 
     super.visitDeclaredVariablePattern(node);
   }
diff --git a/analyzer/lib/src/error/use_result_verifier.dart b/analyzer/lib/src/error/use_result_verifier.dart
index 17be259..fb02494 100644
--- a/analyzer/lib/src/error/use_result_verifier.dart
+++ b/analyzer/lib/src/error/use_result_verifier.dart
@@ -7,7 +7,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
-import 'package:analyzer/src/dart/error/hint_codes.dart';
+import 'package:analyzer/src/error/codes.g.dart';
 import 'package:collection/collection.dart';
 
 class UseResultVerifier {
@@ -101,9 +101,9 @@
     var message = _getUseResultMessage(annotation);
     if (message == null || message.isEmpty) {
       _errorReporter.reportErrorForNode(
-          HintCode.UNUSED_RESULT, toAnnotate, [displayName]);
+          WarningCode.UNUSED_RESULT, toAnnotate, [displayName]);
     } else {
-      _errorReporter.reportErrorForNode(HintCode.UNUSED_RESULT_WITH_MESSAGE,
+      _errorReporter.reportErrorForNode(WarningCode.UNUSED_RESULT_WITH_MESSAGE,
           toAnnotate, [displayName, message]);
     }
   }
diff --git a/analyzer/lib/src/fasta/ast_builder.dart b/analyzer/lib/src/fasta/ast_builder.dart
index f6b47d9..7137edd 100644
--- a/analyzer/lib/src/fasta/ast_builder.dart
+++ b/analyzer/lib/src/fasta/ast_builder.dart
@@ -4755,7 +4755,13 @@
     var typeParameters = pop() as TypeParameterListImpl?;
     var name = pop() as SimpleIdentifierImpl;
     var metadata = pop() as List<AnnotationImpl>?;
-    var comment = _findComment(metadata, mixinKeyword);
+
+    final begin = sealedKeyword ??
+        baseKeyword ??
+        interfaceKeyword ??
+        finalKeyword ??
+        mixinKeyword;
+    var comment = _findComment(metadata, begin);
 
     _classLikeBuilder = _MixinDeclarationBuilder(
       comment: comment,
@@ -5478,7 +5484,8 @@
   }
 
   @override
-  void handleValuedFormalParameter(Token equals, Token token) {
+  void handleValuedFormalParameter(
+      Token equals, Token token, FormalParameterKind kind) {
     assert(optional('=', equals) || optional(':', equals));
     debugEvent("ValuedFormalParameter");
 
diff --git a/analyzer/lib/src/generated/error_detection_helpers.dart b/analyzer/lib/src/generated/error_detection_helpers.dart
index e3f078a..2b8d774 100644
--- a/analyzer/lib/src/generated/error_detection_helpers.dart
+++ b/analyzer/lib/src/generated/error_detection_helpers.dart
@@ -34,7 +34,8 @@
       DartType actualStaticType,
       ErrorCode errorCode,
       {Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
-    if (!expectedStaticType.isVoid && checkForUseOfVoidResult(expression)) {
+    if (expectedStaticType is! VoidType &&
+        checkForUseOfVoidResult(expression)) {
       return;
     }
 
@@ -67,7 +68,8 @@
       DartType expectedStaticType,
       ErrorCode errorCode,
       {Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
-    if (!expectedStaticType.isVoid && checkForUseOfVoidResult(expression)) {
+    if (expectedStaticType is! VoidType &&
+        checkForUseOfVoidResult(expression)) {
       return;
     }
 
@@ -82,6 +84,21 @@
         return node;
       }
 
+      if (expectedStaticType is RecordType) {
+        if (actualStaticType is! RecordType &&
+            expectedStaticType.positionalFields.length == 1) {
+          var field = expectedStaticType.positionalFields.first;
+          if (typeSystem.isAssignableTo(field.type, actualStaticType)) {
+            errorReporter.reportErrorForNode(
+              WarningCode.RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA,
+              expression,
+              [],
+            );
+            return;
+          }
+        }
+      }
+
       errorReporter.reportErrorForNode(
         errorCode,
         getErrorNode(expression),
@@ -108,7 +125,7 @@
     // test the static type of the expression
     DartType staticType = expression.typeOrThrow;
     if (typeSystem.isAssignableTo(staticType, fieldType)) {
-      if (!fieldType.isVoid) {
+      if (fieldType is! VoidType) {
         checkForUseOfVoidResult(expression);
       }
       return;
diff --git a/analyzer/lib/src/generated/error_verifier.dart b/analyzer/lib/src/generated/error_verifier.dart
index 1cd5a8e..c7febbe 100644
--- a/analyzer/lib/src/generated/error_verifier.dart
+++ b/analyzer/lib/src/generated/error_verifier.dart
@@ -284,7 +284,7 @@
   bool get _isEnclosingClassFfiStruct {
     var superClass = _enclosingClass?.supertype?.element;
     return superClass != null &&
-        superClass.library.name == 'dart.ffi' &&
+        _isDartFfiLibrary(superClass.library) &&
         superClass.name == 'Struct';
   }
 
@@ -293,7 +293,7 @@
   bool get _isEnclosingClassFfiUnion {
     var superClass = _enclosingClass?.supertype?.element;
     return superClass != null &&
-        superClass.library.name == 'dart.ffi' &&
+        _isDartFfiLibrary(superClass.library) &&
         superClass.name == 'Union';
   }
 
@@ -1696,6 +1696,11 @@
   /// Verifies that the class is not named `Function` and that it doesn't
   /// extends/implements/mixes in `Function`.
   void _checkForBadFunctionUse(ClassDeclaration node) {
+    // With the `class_modifiers` feature `Function` is final.
+    if (_featureSet!.isEnabled(Feature.class_modifiers)) {
+      return;
+    }
+
     var extendsClause = node.extendsClause;
     var implementsClause = node.implementsClause;
     var withClause = node.withClause;
@@ -1746,7 +1751,11 @@
         final interfaceElement = interfaceType.element;
         if (interfaceElement is ClassOrMixinElementImpl &&
             interfaceElement.isBase &&
-            interfaceElement.library != _currentLibrary) {
+            !interfaceElement.isSealed &&
+            interfaceElement.library != _currentLibrary &&
+            !_mayIgnoreClassModifiers(interfaceElement.library)) {
+          // Should this be combined with _checkForImplementsClauseErrorCodes
+          // to avoid double errors if implementing `int`.
           if (interfaceElement is ClassElement) {
             errorReporter.reportErrorForNode(
                 CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
@@ -1836,10 +1845,7 @@
   }
 
   /// Verify that if a class is being mixed in and class modifiers are enabled
-  /// in that class' library, then the mixin application must be in the same
-  /// library as that class declaration.
-  ///
-  /// No error is emitted if the class being mixed in is a mixin class.
+  /// in that class' library, then it must be a mixin class.
   ///
   /// See [CompileTimeErrorCode.CLASS_USED_AS_MIXIN].
   void _checkForClassUsedAsMixin(WithClause? withClause) {
@@ -1850,9 +1856,9 @@
           final withElement = withType.element;
           if (withElement is ClassElementImpl &&
               !withElement.isMixinClass &&
-              withElement.library != _currentLibrary &&
               withElement.library.featureSet
-                  .isEnabled(Feature.class_modifiers)) {
+                  .isEnabled(Feature.class_modifiers) &&
+              !_mayIgnoreClassModifiers(withElement.library)) {
             errorReporter.reportErrorForNode(
                 CompileTimeErrorCode.CLASS_USED_AS_MIXIN,
                 withMixin,
@@ -2862,7 +2868,9 @@
         final element = type.element;
         if (element is ClassElementImpl &&
             element.isFinal &&
-            element.library != _currentLibrary) {
+            !element.isSealed &&
+            element.library != _currentLibrary &&
+            !_mayIgnoreClassModifiers(element.library)) {
           errorReporter.reportErrorForNode(
               CompileTimeErrorCode.FINAL_CLASS_EXTENDED_OUTSIDE_OF_LIBRARY,
               superclass,
@@ -2877,7 +2885,9 @@
           final element = type.element;
           if (element is MixinElementImpl &&
               element.isFinal &&
-              element.library != _currentLibrary) {
+              !element.isSealed &&
+              element.library != _currentLibrary &&
+              !_mayIgnoreClassModifiers(element.library)) {
             errorReporter.reportErrorForNode(
                 CompileTimeErrorCode.FINAL_MIXIN_MIXED_IN_OUTSIDE_OF_LIBRARY,
                 namedType,
@@ -2893,7 +2903,9 @@
           final element = type.element;
           if (element is ClassOrMixinElementImpl &&
               element.isFinal &&
-              element.library != _currentLibrary) {
+              !element.isSealed &&
+              element.library != _currentLibrary &&
+              !_mayIgnoreClassModifiers(element.library)) {
             final ErrorCode errorCode;
             if (element is ClassElement) {
               errorCode = CompileTimeErrorCode
@@ -3113,7 +3125,9 @@
         final superclassElement = superclassType.element;
         if (superclassElement is ClassElementImpl &&
             superclassElement.isInterface &&
-            superclassElement.library != _currentLibrary) {
+            !superclassElement.isSealed &&
+            superclassElement.library != _currentLibrary &&
+            !_mayIgnoreClassModifiers(superclassElement.library)) {
           errorReporter.reportErrorForNode(
               CompileTimeErrorCode.INTERFACE_CLASS_EXTENDED_OUTSIDE_OF_LIBRARY,
               superclass,
@@ -3128,7 +3142,9 @@
           final withElement = withType.element;
           if (withElement is MixinElementImpl &&
               withElement.isInterface &&
-              withElement.library != _currentLibrary) {
+              !withElement.isSealed &&
+              withElement.library != _currentLibrary &&
+              !_mayIgnoreClassModifiers(withElement.library)) {
             errorReporter.reportErrorForNode(
                 CompileTimeErrorCode
                     .INTERFACE_MIXIN_MIXED_IN_OUTSIDE_OF_LIBRARY,
@@ -3581,19 +3597,16 @@
         }
       }
       // Check that the class has 'Object' as their superclass.
-      final supertype = element.supertype;
-      if (superclass != null &&
-          supertype != null &&
-          !supertype.isDartCoreObject) {
+      if (superclass != null && !superclass.typeOrThrow.isDartCoreObject) {
         errorReporter.reportErrorForNode(
-          CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT,
+          CompileTimeErrorCode.MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT,
           superclass,
           [element.name],
         );
       } else if (withClause != null &&
           !(element.isMixinApplication && withClause.mixinTypes.length < 2)) {
         errorReporter.reportErrorForNode(
-          CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT,
+          CompileTimeErrorCode.MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT,
           withClause,
           [element.name],
         );
@@ -3609,9 +3622,6 @@
   /// See [CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT].
   bool _checkForMixinInheritsNotFromObject(
       NamedType mixinName, InterfaceElement mixinElement) {
-    if (mixinElement is EnumElement) {
-      return false;
-    }
     if (mixinElement is! ClassElement) {
       return false;
     }
@@ -4014,7 +4024,7 @@
     var annotation = declaration.returnType;
     if (annotation != null) {
       DartType type = annotation.typeOrThrow;
-      if (!type.isVoid) {
+      if (type is! VoidType) {
         errorReporter.reportErrorForNode(
             CompileTimeErrorCode.NON_VOID_RETURN_FOR_OPERATOR, annotation);
       }
@@ -4028,7 +4038,7 @@
   void _checkForNonVoidReturnTypeForSetter(TypeAnnotation? namedType) {
     if (namedType != null) {
       DartType type = namedType.typeOrThrow;
-      if (!type.isVoid) {
+      if (type is! VoidType) {
         errorReporter.reportErrorForNode(
             CompileTimeErrorCode.NON_VOID_RETURN_FOR_SETTER, namedType);
       }
@@ -5453,6 +5463,9 @@
     return false;
   }
 
+  /// Returns `true` if the given [library] is the `dart:ffi` library.
+  bool _isDartFfiLibrary(LibraryElement library) => library.name == 'dart.ffi';
+
   /// Return `true` if the given [identifier] is in a location where it is
   /// allowed to resolve to a static member of a supertype.
   bool _isUnqualifiedReferenceToNonLocalStaticMemberAllowed(
@@ -5485,6 +5498,31 @@
     return false;
   }
 
+  /// Checks whether a `final`, `base` or `interface` modifier can be ignored.
+  ///
+  /// Checks whether a subclass in the current library
+  /// can ignore a class modifier of a declaration in [superLibrary].
+  ///
+  /// Only true if the supertype library is a platform library, and
+  /// either the current library is also a platform library,
+  /// or the current library has a language version which predates
+  /// class modifiers
+  bool _mayIgnoreClassModifiers(LibraryElement superLibrary) {
+    // Only modifiers in platform libraries can be ignored.
+    if (!superLibrary.isInSdk) return false;
+
+    // Modifiers in 'dart:ffi' can't be ignored in pre-feature code.
+    if (_isDartFfiLibrary(superLibrary)) {
+      return false;
+    }
+
+    // Other platform libraries can ignore modifiers.
+    if (_currentLibrary.isInSdk) return true;
+
+    // Libraries predating class modifiers can ignore platform modifiers.
+    return !_currentLibrary.featureSet.isEnabled(Feature.class_modifiers);
+  }
+
   /// Return the name of the [parameter], or `null` if the parameter does not
   /// have a name.
   Token? _parameterName(FormalParameter parameter) {
diff --git a/analyzer/lib/src/generated/exhaustiveness.dart b/analyzer/lib/src/generated/exhaustiveness.dart
index 647ff96..e41ed36 100644
--- a/analyzer/lib/src/generated/exhaustiveness.dart
+++ b/analyzer/lib/src/generated/exhaustiveness.dart
@@ -3,9 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/key.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/shared.dart';
 import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
 import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
-import 'package:_fe_analyzer_shared/src/exhaustiveness/static_types.dart';
+import 'package:_fe_analyzer_shared/src/exhaustiveness/types.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
@@ -13,114 +16,12 @@
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
 import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/replacement_visitor.dart';
+import 'package:analyzer/src/dart/element/type_algebra.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
+import 'package:analyzer/src/dart/resolver/variance.dart';
 import 'package:analyzer/src/generated/constant.dart';
 
-Space convertConstantValueToSpace(
-    AnalyzerExhaustivenessCache cache, DartObjectImpl? constantValue) {
-  if (constantValue != null) {
-    InstanceState state = constantValue.state;
-    if (constantValue.isNull) {
-      return Space.nullSpace;
-    } else if (state is BoolState && state.value != null) {
-      return Space(cache.getBoolValueStaticType(state.value!));
-    } else if (state is RecordState) {
-      Map<String, Space> fields = {};
-      for (int index = 0; index < state.positionalFields.length; index++) {
-        fields['\$${index + 1}'] =
-            convertConstantValueToSpace(cache, state.positionalFields[index]);
-      }
-      for (MapEntry<String, DartObjectImpl> entry
-          in state.namedFields.entries) {
-        fields[entry.key] = convertConstantValueToSpace(cache, entry.value);
-      }
-      return Space(cache.getStaticType(constantValue.type), fields);
-    }
-    DartType type = constantValue.type;
-    if (type is InterfaceType && type.element.kind == ElementKind.ENUM) {
-      return Space(cache.getEnumElementStaticType(
-          type.element as EnumElement, constantValue));
-    }
-    return Space(cache.getUniqueStaticType(
-        type, constantValue, constantValue.toString()));
-  }
-  // TODO(johnniwinther): Assert that constant value is available when the
-  // exhaustiveness checking is complete.
-  return Space(cache.getUnknownStaticType());
-}
-
-Space convertPatternToSpace(
-    AnalyzerExhaustivenessCache cache,
-    DartPattern pattern,
-    Map<ConstantPattern, DartObjectImpl> constantPatternValues) {
-  if (pattern is DeclaredVariablePatternImpl) {
-    DartType type = pattern.declaredElement!.type;
-    return Space(cache.getStaticType(type));
-  } else if (pattern is ObjectPattern) {
-    Map<String, Space> fields = {};
-    for (PatternField field in pattern.fields) {
-      PatternFieldName? fieldName = field.name;
-      String? name;
-      if (fieldName?.name != null) {
-        name = fieldName!.name!.lexeme;
-      } else {
-        name = field.element?.name;
-      }
-      if (name == null) {
-        // TODO(johnniwinther): How do we handle error cases?
-        continue;
-      }
-      fields[name] =
-          convertPatternToSpace(cache, field.pattern, constantPatternValues);
-    }
-    final type = pattern.type.typeOrThrow;
-    return Space(cache.getStaticType(type), fields);
-  } else if (pattern is WildcardPattern) {
-    final typeNode = pattern.type;
-    if (typeNode == null) {
-      return Space.top;
-    } else {
-      final type = typeNode.typeOrThrow;
-      return Space(cache.getStaticType(type));
-    }
-  } else if (pattern is RecordPattern) {
-    int index = 1;
-    Map<String, Space> fields = {};
-    List<DartType> positional = [];
-    Map<String, DartType> named = {};
-    for (PatternField field in pattern.fields) {
-      PatternFieldName? fieldName = (field as PatternFieldImpl).name;
-      String? name;
-      if (fieldName == null) {
-        name = '\$${index++}';
-        positional.add(cache.typeSystem.typeProvider.dynamicType);
-      } else {
-        if (fieldName.name != null) {
-          name = fieldName.name!.lexeme;
-        } else {
-          name = field.pattern.variablePattern?.name.lexeme;
-        }
-        if (name != null) {
-          named[name] = cache.typeSystem.typeProvider.dynamicType;
-        } else {
-          // Error case, skip field.
-          continue;
-        }
-      }
-      fields[name] =
-          convertPatternToSpace(cache, field.pattern, constantPatternValues);
-    }
-    RecordType recordType = RecordType(
-        positional: positional,
-        named: named,
-        nullabilitySuffix: NullabilitySuffix.none);
-    return Space(cache.getStaticType(recordType), fields);
-  }
-  // TODO(johnniwinther): Handle remaining patterns.
-  DartObjectImpl? value = constantPatternValues[pattern];
-  return convertConstantValueToSpace(cache, value);
-}
-
 class AnalyzerEnumOperations
     implements EnumOperations<DartType, EnumElement, FieldElement, DartObject> {
   const AnalyzerEnumOperations();
@@ -167,23 +68,41 @@
       : super(
             AnalyzerTypeOperations(typeSystem),
             const AnalyzerEnumOperations(),
-            const AnalyzerSealedClassOperations());
+            AnalyzerSealedClassOperations(typeSystem));
 }
 
 class AnalyzerSealedClassOperations
     implements SealedClassOperations<DartType, ClassElement> {
-  const AnalyzerSealedClassOperations();
+  final TypeSystemImpl _typeSystem;
+
+  AnalyzerSealedClassOperations(this._typeSystem);
 
   @override
   List<ClassElement> getDirectSubclasses(ClassElement sealedClass) {
     List<ClassElement> subclasses = [];
     LibraryElement library = sealedClass.library;
+    outer:
     for (Element declaration in library.topLevelElements) {
       if (declaration != sealedClass && declaration is ClassElement) {
-        for (InterfaceType supertype in declaration.allSupertypes) {
-          if (supertype.element == sealedClass) {
+        bool checkType(InterfaceType? type) {
+          if (type?.element == sealedClass) {
             subclasses.add(declaration);
-            break;
+            return true;
+          }
+          return false;
+        }
+
+        if (checkType(declaration.supertype)) {
+          continue outer;
+        }
+        for (InterfaceType mixin in declaration.mixins) {
+          if (checkType(mixin)) {
+            continue outer;
+          }
+        }
+        for (InterfaceType interface in declaration.interfaces) {
+          if (checkType(interface)) {
+            continue outer;
           }
         }
       }
@@ -194,7 +113,7 @@
   @override
   ClassElement? getSealedClass(DartType type) {
     Element? element = type.element;
-    if (element is ClassElementImpl && element.isSealed) {
+    if (element is ClassElement && element.isSealed) {
       return element;
     }
     return null;
@@ -202,17 +121,51 @@
 
   @override
   DartType? getSubclassAsInstanceOf(
-      ClassElement subClass, DartType sealedClassType) {
-    // TODO(johnniwinther): Handle generic types.
-    return subClass.thisType;
+      ClassElement subClass, covariant InterfaceType sealedClassType) {
+    InterfaceType thisType = subClass.thisType;
+    InterfaceType asSealedClass =
+        thisType.asInstanceOf(sealedClassType.element)!;
+    if (thisType.typeArguments.isEmpty) {
+      return thisType;
+    }
+    bool trivialSubstitution = true;
+    if (thisType.typeArguments.length == asSealedClass.typeArguments.length) {
+      for (int i = 0; i < thisType.typeArguments.length; i++) {
+        if (thisType.typeArguments[i] != asSealedClass.typeArguments[i]) {
+          trivialSubstitution = false;
+          break;
+        }
+      }
+      if (trivialSubstitution) {
+        Substitution substitution = Substitution.fromPairs(
+            subClass.typeParameters, sealedClassType.typeArguments);
+        for (int i = 0; i < subClass.typeParameters.length; i++) {
+          DartType? bound = subClass.typeParameters[i].bound;
+          if (bound != null &&
+              !_typeSystem.isSubtypeOf(sealedClassType.typeArguments[i],
+                  substitution.substituteType(bound))) {
+            trivialSubstitution = false;
+            break;
+          }
+        }
+      }
+    } else {
+      trivialSubstitution = false;
+    }
+    if (trivialSubstitution) {
+      return subClass.instantiate(
+          typeArguments: sealedClassType.typeArguments,
+          nullabilitySuffix: NullabilitySuffix.none);
+    } else {
+      return TypeParameterReplacer.replaceTypeVariables(_typeSystem, thisType);
+    }
   }
 }
 
 class AnalyzerTypeOperations implements TypeOperations<DartType> {
   final TypeSystemImpl _typeSystem;
 
-  final Map<InterfaceType, Map<String, DartType>> _interfaceFieldTypesCaches =
-      {};
+  final Map<InterfaceType, Map<Key, DartType>> _interfaceFieldTypesCaches = {};
 
   AnalyzerTypeOperations(this._typeSystem);
 
@@ -220,24 +173,58 @@
   DartType get boolType => _typeSystem.typeProvider.boolType;
 
   @override
+  DartType get nonNullableObjectType => _typeSystem.objectNone;
+
+  @override
   DartType get nullableObjectType => _typeSystem.objectQuestion;
 
   @override
-  Map<String, DartType> getFieldTypes(DartType type) {
+  Map<Key, DartType> getFieldTypes(DartType type) {
     if (type is InterfaceType) {
       return _getInterfaceFieldTypes(type);
     } else if (type is RecordType) {
-      Map<String, DartType> fieldTypes = {};
+      Map<Key, DartType> fieldTypes = {};
+      fieldTypes.addAll(getFieldTypes(_typeSystem.typeProvider.objectType));
       for (int index = 0; index < type.positionalFields.length; index++) {
         RecordTypePositionalField field = type.positionalFields[index];
-        fieldTypes['\$${index + 1}'] = field.type;
+        fieldTypes[RecordIndexKey(index)] = field.type;
       }
       for (RecordTypeNamedField field in type.namedFields) {
-        fieldTypes[field.name] = field.type;
+        fieldTypes[RecordNameKey(field.name)] = field.type;
       }
       return fieldTypes;
     }
-    return const {};
+    return getFieldTypes(_typeSystem.typeProvider.objectType);
+  }
+
+  @override
+  DartType? getFutureOrTypeArgument(DartType type) {
+    return type.isDartAsyncFutureOr ? _typeSystem.futureOrBase(type) : null;
+  }
+
+  @override
+  DartType? getListElementType(DartType type) {
+    InterfaceType? listType =
+        type.asInstanceOf(_typeSystem.typeProvider.listElement);
+    if (listType != null) {
+      return listType.typeArguments[0];
+    }
+    return null;
+  }
+
+  @override
+  DartType? getListType(DartType type) {
+    return type.asInstanceOf(_typeSystem.typeProvider.listElement);
+  }
+
+  @override
+  DartType? getMapValueType(DartType type) {
+    InterfaceType? mapType =
+        type.asInstanceOf(_typeSystem.typeProvider.mapElement);
+    if (mapType != null) {
+      return mapType.typeArguments[1];
+    }
+    return null;
   }
 
   @override
@@ -246,11 +233,36 @@
   }
 
   @override
+  bool hasSimpleName(DartType type) {
+    return type is InterfaceType ||
+        type is DynamicType ||
+        type is VoidType ||
+        type is NeverType ||
+        // TODO(johnniwinther): What about intersection types?
+        type is TypeParameterType;
+  }
+
+  @override
+  DartType instantiateFuture(DartType type) {
+    return _typeSystem.typeProvider.futureType(type);
+  }
+
+  @override
   bool isBoolType(DartType type) {
     return type.isDartCoreBool && !isNullable(type);
   }
 
   @override
+  bool isDynamic(DartType type) {
+    return type is DynamicType;
+  }
+
+  @override
+  bool isGeneric(DartType type) {
+    return type is InterfaceType && type.typeArguments.isNotEmpty;
+  }
+
+  @override
   bool isNeverType(DartType type) {
     return type is NeverType;
   }
@@ -286,10 +298,15 @@
   }
 
   @override
+  DartType overapproximate(DartType type) {
+    return TypeParameterReplacer.replaceTypeVariables(_typeSystem, type);
+  }
+
+  @override
   String typeToString(DartType type) => type.toString();
 
-  Map<String, DartType> _getInterfaceFieldTypes(InterfaceType type) {
-    Map<String, DartType>? fieldTypes = _interfaceFieldTypesCaches[type];
+  Map<Key, DartType> _getInterfaceFieldTypes(InterfaceType type) {
+    Map<Key, DartType>? fieldTypes = _interfaceFieldTypesCaches[type];
     if (fieldTypes == null) {
       _interfaceFieldTypesCaches[type] = fieldTypes = {};
       for (InterfaceType supertype in type.allSupertypes) {
@@ -297,7 +314,12 @@
       }
       for (PropertyAccessorElement accessor in type.accessors) {
         if (accessor.isGetter && !accessor.isStatic) {
-          fieldTypes[accessor.name] = accessor.type.returnType;
+          fieldTypes[NameKey(accessor.name)] = accessor.type.returnType;
+        }
+      }
+      for (MethodElement method in type.methods) {
+        if (!method.isStatic) {
+          fieldTypes[NameKey(method.name)] = method.type;
         }
       }
     }
@@ -308,18 +330,287 @@
 /// Data gathered by the exhaustiveness computation, retained for testing
 /// purposes.
 class ExhaustivenessDataForTesting {
+  /// Access to interface for looking up `Object` members on non-interface
+  /// types.
+  final ObjectFieldLookup objectFieldLookup;
+
   /// Map from switch statement/expression nodes to the static type of the
   /// scrutinee.
   Map<AstNode, StaticType> switchScrutineeType = {};
 
+  /// Map from switch statement/expression nodes the spaces for its cases.
+  Map<AstNode, List<Space>> switchCases = {};
+
   /// Map from switch case nodes to the space for its pattern/expression.
   Map<AstNode, Space> caseSpaces = {};
 
-  /// Map from switch case nodes to the remaining space before the case or
-  /// from statement/expression nodes to the remaining space after all cases.
-  Map<AstNode, Space> remainingSpaces = {};
-
   /// Map from switch statement/expression/case nodes to the error reported
   /// on the node.
   Map<AstNode, ExhaustivenessError> errors = {};
+
+  ExhaustivenessDataForTesting(this.objectFieldLookup);
+}
+
+class PatternConverter with SpaceCreator<DartPattern, DartType> {
+  final AnalyzerExhaustivenessCache cache;
+  final Map<Expression, DartObjectImpl> mapPatternKeyValues;
+  final Map<ConstantPattern, DartObjectImpl> constantPatternValues;
+
+  PatternConverter({
+    required this.cache,
+    required this.mapPatternKeyValues,
+    required this.constantPatternValues,
+  });
+
+  @override
+  ObjectFieldLookup get objectFieldLookup => cache;
+
+  @override
+  TypeOperations<DartType> get typeOperations => cache.typeOperations;
+
+  @override
+  StaticType createListType(
+      DartType type, ListTypeIdentity<DartType> identity) {
+    return cache.getListStaticType(type, identity);
+  }
+
+  @override
+  StaticType createMapType(DartType type, MapTypeIdentity<DartType> identity) {
+    return cache.getMapStaticType(type, identity);
+  }
+
+  @override
+  StaticType createStaticType(DartType type) {
+    return cache.getStaticType(type);
+  }
+
+  @override
+  StaticType createUnknownStaticType() {
+    return cache.getUnknownStaticType();
+  }
+
+  @override
+  Space dispatchPattern(Path path, StaticType contextType, DartPattern pattern,
+      {required bool nonNull}) {
+    if (pattern is DeclaredVariablePatternImpl) {
+      return createVariableSpace(
+          path, contextType, pattern.declaredElement!.type,
+          nonNull: nonNull);
+    } else if (pattern is ObjectPattern) {
+      final fields = <String, DartPattern>{};
+      for (final field in pattern.fields) {
+        final name = field.effectiveName;
+        if (name == null) {
+          // Error case, skip field.
+          continue;
+        }
+        fields[name] = field.pattern;
+      }
+      return createObjectSpace(
+          path, contextType, pattern.type.typeOrThrow, fields,
+          nonNull: nonNull);
+    } else if (pattern is WildcardPattern) {
+      return createWildcardSpace(path, contextType, pattern.type?.typeOrThrow,
+          nonNull: nonNull);
+    } else if (pattern is RecordPatternImpl) {
+      final positionalTypes = <DartType>[];
+      final positionalPatterns = <DartPattern>[];
+      final namedTypes = <String, DartType>{};
+      final namedPatterns = <String, DartPattern>{};
+      for (final field in pattern.fields) {
+        final nameNode = field.name;
+        if (nameNode == null) {
+          positionalTypes.add(cache.typeSystem.typeProvider.dynamicType);
+          positionalPatterns.add(field.pattern);
+        } else {
+          String? name = field.effectiveName;
+          if (name != null) {
+            namedTypes[name] = cache.typeSystem.typeProvider.dynamicType;
+            namedPatterns[name] = field.pattern;
+          } else {
+            // Error case, skip field.
+            continue;
+          }
+        }
+      }
+      final recordType = RecordType(
+        positional: positionalTypes,
+        named: namedTypes,
+        nullabilitySuffix: NullabilitySuffix.none,
+      );
+      return createRecordSpace(
+          path, contextType, recordType, positionalPatterns, namedPatterns);
+    } else if (pattern is LogicalOrPattern) {
+      return createLogicalOrSpace(
+          path, contextType, pattern.leftOperand, pattern.rightOperand,
+          nonNull: nonNull);
+    } else if (pattern is NullCheckPattern) {
+      return createNullCheckSpace(path, contextType, pattern.pattern);
+    } else if (pattern is ParenthesizedPattern) {
+      return dispatchPattern(path, contextType, pattern.pattern,
+          nonNull: nonNull);
+    } else if (pattern is NullAssertPattern) {
+      return createNullAssertSpace(path, contextType, pattern.pattern);
+    } else if (pattern is CastPattern) {
+      return createCastSpace(
+          path, contextType, pattern.type.typeOrThrow, pattern.pattern,
+          nonNull: nonNull);
+    } else if (pattern is LogicalAndPattern) {
+      return createLogicalAndSpace(
+          path, contextType, pattern.leftOperand, pattern.rightOperand,
+          nonNull: nonNull);
+    } else if (pattern is RelationalPattern) {
+      return createRelationalSpace(path);
+    } else if (pattern is ListPattern) {
+      DartType? elementType;
+      var typeArguments = pattern.typeArguments;
+      if (typeArguments != null && typeArguments.arguments.length == 1) {
+        elementType = typeArguments.arguments[0].typeOrThrow;
+      }
+      elementType ??= cache.typeSystem.typeProvider.dynamicType;
+      List<DartPattern> headElements = [];
+      DartPattern? restElement;
+      List<DartPattern> tailElements = [];
+      bool hasRest = false;
+      for (ListPatternElement element in pattern.elements) {
+        if (element is RestPatternElement) {
+          restElement = element.pattern;
+          hasRest = true;
+        } else if (hasRest) {
+          tailElements.add(element as DartPattern);
+        } else {
+          headElements.add(element as DartPattern);
+        }
+      }
+      return createListSpace(path,
+          type: cache.typeSystem.typeProvider.listType(elementType),
+          elementType: elementType,
+          headElements: headElements,
+          tailElements: tailElements,
+          restElement: restElement,
+          hasRest: hasRest,
+          hasExplicitTypeArgument: pattern.typeArguments != null);
+    } else if (pattern is MapPattern) {
+      DartType? keyType;
+      DartType? valueType;
+      var typeArguments = pattern.typeArguments;
+      if (typeArguments != null && typeArguments.arguments.length == 2) {
+        keyType = typeArguments.arguments[0].typeOrThrow;
+        valueType = typeArguments.arguments[1].typeOrThrow;
+      }
+      keyType ??= cache.typeSystem.typeProvider.dynamicType;
+      valueType ??= cache.typeSystem.typeProvider.dynamicType;
+      bool hasRest = false;
+      Map<MapKey, DartPattern> entries = {};
+      for (MapPatternElement entry in pattern.elements) {
+        if (entry is RestPatternElement) {
+          hasRest = true;
+        } else {
+          Expression expression = (entry as MapPatternEntry).key;
+          // TODO(johnniwinther): Assert that we have a constant value.
+          DartObjectImpl? constant = mapPatternKeyValues[expression];
+          if (constant == null) {
+            return createUnknownSpace(path);
+          }
+          MapKey key = MapKey(constant, constant.state.toString());
+          entries[key] = entry.value;
+        }
+      }
+
+      return createMapSpace(path,
+          type: cache.typeSystem.typeProvider.mapType(keyType, valueType),
+          keyType: keyType,
+          valueType: valueType,
+          entries: entries,
+          hasRest: hasRest,
+          hasExplicitTypeArguments: pattern.typeArguments != null);
+    } else if (pattern is ConstantPattern) {
+      final value = constantPatternValues[pattern];
+      if (value != null) {
+        return _convertConstantValue(value, path);
+      }
+      return createUnknownSpace(path);
+    }
+    assert(false, "Unexpected pattern $pattern (${pattern.runtimeType})");
+    return createUnknownSpace(path);
+  }
+
+  Space _convertConstantValue(DartObjectImpl value, Path path) {
+    final state = value.state;
+    if (value.isNull) {
+      return Space(path, StaticType.nullType);
+    } else if (state is BoolState) {
+      final value = state.value;
+      if (value != null) {
+        return Space(path, cache.getBoolValueStaticType(state.value!));
+      }
+    } else if (state is RecordState) {
+      final fields = <Key, Space>{};
+      for (var index = 0; index < state.positionalFields.length; index++) {
+        final key = RecordIndexKey(index);
+        final value = state.positionalFields[index];
+        fields[key] = _convertConstantValue(value, path.add(key));
+      }
+      for (final entry in state.namedFields.entries) {
+        final key = RecordNameKey(entry.key);
+        fields[key] = _convertConstantValue(entry.value, path.add(key));
+      }
+      return Space(path, cache.getStaticType(value.type), fields: fields);
+    }
+    final type = value.type;
+    if (type is InterfaceType) {
+      final element = type.element;
+      if (element is EnumElement) {
+        return Space(path, cache.getEnumElementStaticType(element, value));
+      }
+    }
+    return Space(
+        path,
+        cache.getUniqueStaticType<DartObjectImpl>(
+            type, value, value.state.toString()));
+  }
+}
+
+class TypeParameterReplacer extends ReplacementVisitor {
+  final TypeSystemImpl _typeSystem;
+  Variance _variance = Variance.covariant;
+
+  TypeParameterReplacer(this._typeSystem);
+
+  @override
+  void changeVariance() {
+    if (_variance == Variance.covariant) {
+      _variance = Variance.contravariant;
+    } else if (_variance == Variance.contravariant) {
+      _variance = Variance.covariant;
+    }
+  }
+
+  @override
+  DartType? visitTypeParameterBound(DartType type) {
+    Variance savedVariance = _variance;
+    _variance = Variance.invariant;
+    DartType? result = type.accept(this);
+    _variance = savedVariance;
+    return result;
+  }
+
+  @override
+  DartType? visitTypeParameterType(TypeParameterType node) {
+    if (_variance == Variance.contravariant) {
+      return _replaceTypeParameterTypes(_typeSystem.typeProvider.neverType);
+    } else {
+      return _replaceTypeParameterTypes(
+          (node.element as TypeParameterElementImpl).defaultType!);
+    }
+  }
+
+  DartType _replaceTypeParameterTypes(DartType type) {
+    return type.accept(this) ?? type;
+  }
+
+  static DartType replaceTypeVariables(
+      TypeSystemImpl typeSystem, DartType type) {
+    return TypeParameterReplacer(typeSystem)._replaceTypeParameterTypes(type);
+  }
 }
diff --git a/analyzer/lib/src/generated/ffi_verifier.dart b/analyzer/lib/src/generated/ffi_verifier.dart
index 1bb49c9..eeb0ea0 100644
--- a/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/analyzer/lib/src/generated/ffi_verifier.dart
@@ -899,8 +899,8 @@
     } else if (nativeReturnType == _PrimitiveDartType.bool) {
       return dartType.isDartCoreBool;
     } else if (nativeReturnType == _PrimitiveDartType.void_) {
-      return dartType.isVoid;
-    } else if (dartType.isVoid) {
+      return dartType is VoidType;
+    } else if (dartType is VoidType) {
       // Don't allow other native subtypes if the Dart return type is void.
       return nativeReturnType == _PrimitiveDartType.void_;
     } else if (nativeReturnType == _PrimitiveDartType.handle) {
@@ -1077,7 +1077,7 @@
 
     // TODO(brianwilkerson) Validate that `f` is a top-level function.
     final DartType R = (T as FunctionType).returnType;
-    if ((FT as FunctionType).returnType.isVoid ||
+    if ((FT as FunctionType).returnType is VoidType ||
         R.isPointer ||
         R.isHandle ||
         R.isCompoundSubtype) {
diff --git a/analyzer/lib/src/generated/resolver.dart b/analyzer/lib/src/generated/resolver.dart
index a32e3b6..4f1ffad 100644
--- a/analyzer/lib/src/generated/resolver.dart
+++ b/analyzer/lib/src/generated/resolver.dart
@@ -72,6 +72,7 @@
 import 'package:analyzer/src/dart/resolver/variable_declaration_resolver.dart';
 import 'package:analyzer/src/dart/resolver/yield_statement_resolver.dart';
 import 'package:analyzer/src/diagnostic/diagnostic.dart';
+import 'package:analyzer/src/error/base_or_final_type_verifier.dart';
 import 'package:analyzer/src/error/bool_expression_verifier.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/error/dead_code_verifier.dart';
@@ -151,7 +152,7 @@
     with
         ErrorDetectionHelpers,
         TypeAnalyzer<AstNode, Statement, Expression, PromotableElement,
-            DartType, DartPattern> {
+            DartType, DartPattern, void> {
   /// Debug-only: if `true`, manipulations of [_rewriteStack] performed by
   /// [popRewrite], [pushRewrite], and [replaceExpression] will be printed.
   static const bool _debugRewriteStack = false;
@@ -202,6 +203,10 @@
 
   final MigrationResolutionHooks? migrationResolutionHooks;
 
+  /// Helper for checking that subtypes of a base or final type must be base,
+  /// final, or sealed.
+  late final BaseOrFinalTypeVerifier baseOrFinalTypeVerifier;
+
   /// Helper for checking expression that should have the `bool` type.
   late final BoolExpressionVerifier boolExpressionVerifier;
 
@@ -372,6 +377,8 @@
       errorReporter: errorReporter,
       resolver: this,
     );
+    baseOrFinalTypeVerifier = BaseOrFinalTypeVerifier(
+        definingLibrary: definingLibrary, errorReporter: errorReporter);
     boolExpressionVerifier = BoolExpressionVerifier(
       resolver: this,
       errorReporter: errorReporter,
@@ -610,7 +617,7 @@
         errorCode = CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY;
       } else {
         var returnTypeBase = typeSystem.futureOrBase(returnType);
-        if (returnTypeBase.isVoid ||
+        if (returnTypeBase is VoidType ||
             returnTypeBase.isDynamic ||
             returnTypeBase.isDartCoreNull) {
           return;
@@ -878,20 +885,36 @@
   void finishJoinedPatternVariable(
     covariant JoinPatternVariableElementImpl variable, {
     required JoinedPatternVariableLocation location,
-    required bool isConsistent,
+    required shared.JoinedPatternVariableInconsistency inconsistency,
     required bool isFinal,
     required DartType type,
   }) {
-    variable.isConsistent &= isConsistent;
+    variable.inconsistency = variable.inconsistency.maxWith(inconsistency);
     variable.isFinal = isFinal;
     variable.type = type;
 
     if (location == JoinedPatternVariableLocation.sharedCaseScope) {
       for (var reference in variable.references) {
-        if (!variable.isConsistent) {
+        if (variable.inconsistency ==
+            shared.JoinedPatternVariableInconsistency.sharedCaseAbsent) {
           errorReporter.reportErrorForNode(
             CompileTimeErrorCode
-                .INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE,
+                .PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES,
+            reference,
+            [variable.name],
+          );
+        } else if (variable.inconsistency ==
+            shared.JoinedPatternVariableInconsistency.sharedCaseHasLabel) {
+          errorReporter.reportErrorForNode(
+            CompileTimeErrorCode.PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL,
+            reference,
+            [variable.name],
+          );
+        } else if (variable.inconsistency ==
+            shared.JoinedPatternVariableInconsistency.differentFinalityOrType) {
+          errorReporter.reportErrorForNode(
+            CompileTimeErrorCode
+                .PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE,
             reference,
             [variable.name],
           );
@@ -992,9 +1015,9 @@
 
     var group = node.memberGroups[index];
     return SwitchStatementMemberInfo(
-      group.members.map(ofMember).toList(),
-      group.statements,
-      group.variables,
+      heads: group.members.map(ofMember).toList(),
+      body: group.statements,
+      variables: group.variables,
       hasLabels: group.hasLabels,
     );
   }
@@ -1060,8 +1083,9 @@
       AstNode node, int caseIndex, Iterable<PromotableElement> variables) {}
 
   @override
-  void handleCaseHead(
-    covariant AstNodeImpl node, {
+  CaseHeadOrDefaultInfo<AstNode, Expression, PromotableElement> handleCaseHead(
+    covariant AstNodeImpl node,
+    CaseHeadOrDefaultInfo<AstNode, Expression, PromotableElement> head, {
     required int caseIndex,
     required int subIndex,
   }) {
@@ -1072,7 +1096,12 @@
       final group = node.memberGroups[caseIndex];
       legacySwitchExhaustiveness?.visitSwitchMember(group);
       nullSafetyDeadCodeVerifier.flowEnd(group.members[subIndex]);
+    } else if (node is SwitchExpressionImpl) {
+      legacySwitchExhaustiveness
+          ?.visitSwitchExpressionCase(node.cases[caseIndex]);
     }
+
+    return head;
   }
 
   @override
@@ -1096,6 +1125,7 @@
   void handleMapPatternEntry(
     DartPattern container,
     covariant MapPatternEntryImpl entry,
+    DartType keyType,
   ) {
     entry.key = popRewrite()!;
   }
@@ -1534,7 +1564,7 @@
       node,
       typeArguments: typeArguments,
       elements: node.elements,
-    );
+    ).requiredType;
   }
 
   @override
@@ -2028,6 +2058,9 @@
     } finally {
       enclosingClass = outerType;
     }
+
+    baseOrFinalTypeVerifier.checkElement(
+        node.declaredElement as ClassOrMixinElementImpl, node.implementsClause);
   }
 
   @override
@@ -2035,6 +2068,8 @@
     checkUnreachableNode(node);
     node.visitChildren(this);
     elementResolver.visitClassTypeAlias(node);
+    baseOrFinalTypeVerifier.checkElement(
+        node.declaredElement as ClassOrMixinElementImpl, node.implementsClause);
   }
 
   @override
@@ -2953,6 +2988,9 @@
     } finally {
       enclosingClass = outerType;
     }
+
+    baseOrFinalTypeVerifier.checkElement(
+        node.declaredElement as ClassOrMixinElementImpl, node.implementsClause);
   }
 
   @override
@@ -3570,7 +3608,7 @@
         final targetFutureType = instanceOfFuture.typeArguments.first;
         final expectedReturnType = typeProvider.futureOrType(targetFutureType);
         final returnTypeBase = typeSystem.futureOrBase(expectedReturnType);
-        if (returnTypeBase.isVoid ||
+        if (returnTypeBase is VoidType ||
             returnTypeBase.isDynamic ||
             returnTypeBase.isDartCoreNull) {
           return;
@@ -3638,13 +3676,7 @@
       genericMetadataIsEnabled: genericMetadataIsEnabled,
     );
     inferrer.constrainReturnType(declaredType, contextType);
-    return inferrer.partialInfer().map((typeArgument) {
-      if (typeArgument is UnknownInferredType) {
-        return typeProvider.dynamicType;
-      } else {
-        return typeArgument;
-      }
-    }).toList();
+    return inferrer.upwardsInfer();
   }
 
   /// If `expression` should be treated as `expression.call`, inserts an
@@ -3942,16 +3974,12 @@
     } else if (nameNode is EnumConstantArguments) {
       var parent = nameNode.parent;
       if (parent is EnumConstantDeclaration) {
-        var declaredElement = parent.declaredElement;
-        if (declaredElement is VariableElement) {
-          name = declaredElement.type.getDisplayString(withNullability: true);
-        }
-      }
-    } else if (nameNode is EnumConstantDeclaration) {
-      var declaredElement = nameNode.declaredElement;
-      if (declaredElement is VariableElement) {
+        var declaredElement = parent.declaredElement!;
         name = declaredElement.type.getDisplayString(withNullability: true);
       }
+    } else if (nameNode is EnumConstantDeclaration) {
+      var declaredElement = nameNode.declaredElement!;
+      name = declaredElement.type.getDisplayString(withNullability: true);
     } else if (nameNode is Annotation) {
       var nameNodeName = nameNode.name;
       name = nameNodeName is PrefixedIdentifier
@@ -4127,6 +4155,14 @@
   ImplicitLabelScope get implicitLabelScope => _implicitLabelScope;
 
   @override
+  void visitAssignedVariablePattern(AssignedVariablePattern node) {
+    final element = node.element;
+    if (element is PromotableElement) {
+      _localVariableInfo.potentiallyMutatedInScope.add(element);
+    }
+  }
+
+  @override
   void visitBlock(Block node) {
     _withDeclaredLocals(node, node.statements, () {
       super.visitBlock(node);
@@ -4829,6 +4865,7 @@
         _localVariableInfo.potentiallyMutatedInScope.add(element);
         if (_enclosingClosure != null &&
             element.enclosingElement != _enclosingClosure) {
+          // ignore:deprecated_member_use_from_same_package
           _localVariableInfo.potentiallyMutatedInClosure.add(element);
         }
       }
@@ -5094,6 +5131,20 @@
 
   SwitchExhaustiveness._(this._enumConstants, this._isNullEnumValueCovered);
 
+  void visitSwitchExpressionCase(SwitchExpressionCaseImpl node) {
+    if (_enumConstants != null) {
+      ExpressionImpl? caseConstant;
+      var guardedPattern = node.guardedPattern;
+      if (guardedPattern.whenClause == null) {
+        var pattern = guardedPattern.pattern.unParenthesized;
+        if (pattern is ConstantPatternImpl) {
+          caseConstant = pattern.expression;
+        }
+      }
+      _handleCaseConstant(caseConstant);
+    }
+  }
+
   void visitSwitchMember(SwitchStatementCaseGroup group) {
     for (var node in group.members) {
       if (_enumConstants != null) {
@@ -5109,24 +5160,28 @@
             }
           }
         }
-        if (caseConstant != null) {
-          var element = _referencedElement(caseConstant);
-          if (element is PropertyAccessorElement) {
-            _enumConstants!.remove(element.variable);
-          }
-          if (caseConstant is NullLiteral) {
-            _isNullEnumValueCovered = true;
-          }
-          if (_enumConstants!.isEmpty && _isNullEnumValueCovered) {
-            isExhaustive = true;
-          }
-        }
+        _handleCaseConstant(caseConstant);
       } else if (node is SwitchDefault) {
         isExhaustive = true;
       }
     }
   }
 
+  void _handleCaseConstant(ExpressionImpl? caseConstant) {
+    if (caseConstant != null) {
+      var element = _referencedElement(caseConstant);
+      if (element is PropertyAccessorElement) {
+        _enumConstants!.remove(element.variable);
+      }
+      if (caseConstant is NullLiteral) {
+        _isNullEnumValueCovered = true;
+      }
+      if (_enumConstants!.isEmpty && _isNullEnumValueCovered) {
+        isExhaustive = true;
+      }
+    }
+  }
+
   static Element? _referencedElement(Expression expression) {
     if (expression is ParenthesizedExpression) {
       return _referencedElement(expression.expression);
diff --git a/analyzer/lib/src/generated/source.dart b/analyzer/lib/src/generated/source.dart
index ab917f2..16f192c 100644
--- a/analyzer/lib/src/generated/source.dart
+++ b/analyzer/lib/src/generated/source.dart
@@ -66,7 +66,7 @@
   ///
   /// @param uri the URI being tested
   /// @return `true` if the given URI is a `dart:` URI
-  static bool isDartUri(Uri uri) => DART_SCHEME == uri.scheme;
+  static bool isDartUri(Uri uri) => uri.isScheme(DART_SCHEME);
 }
 
 /// An implementation of an non-existing [Source].
diff --git a/analyzer/lib/src/generated/super_context.dart b/analyzer/lib/src/generated/super_context.dart
index 7068c16..bdfd903 100644
--- a/analyzer/lib/src/generated/super_context.dart
+++ b/analyzer/lib/src/generated/super_context.dart
@@ -31,24 +31,32 @@
     for (AstNode? node = expression; node != null; node = node.parent) {
       if (node is Annotation) {
         return SuperContext.annotation;
+      } else if (node is ClassDeclaration) {
+        return SuperContext.valid;
       } else if (node is CompilationUnit) {
         return SuperContext.static;
       } else if (node is ConstructorDeclaration) {
-        return node.factoryKeyword == null
-            ? SuperContext.valid
-            : SuperContext.static;
+        if (node.factoryKeyword != null) {
+          return SuperContext.static;
+        }
       } else if (node is ConstructorInitializer) {
         return SuperContext.static;
+      } else if (node is EnumDeclaration) {
+        return SuperContext.valid;
+      } else if (node is ExtensionDeclaration) {
+        return SuperContext.extension;
       } else if (node is FieldDeclaration) {
-        return node.staticKeyword == null && node.fields.lateKeyword != null
-            ? SuperContext.valid
-            : SuperContext.static;
+        if (node.staticKeyword != null) {
+          return SuperContext.static;
+        }
+        if (node.fields.lateKeyword == null) {
+          return SuperContext.static;
+        }
       } else if (node is MethodDeclaration) {
         if (node.isStatic) {
           return SuperContext.static;
-        } else if (node.parent is ExtensionDeclaration) {
-          return SuperContext.extension;
         }
+      } else if (node is MixinDeclaration) {
         return SuperContext.valid;
       }
     }
diff --git a/analyzer/lib/src/generated/utilities_collection.dart b/analyzer/lib/src/generated/utilities_collection.dart
index c3c0036..8f3f27f 100644
--- a/analyzer/lib/src/generated/utilities_collection.dart
+++ b/analyzer/lib/src/generated/utilities_collection.dart
@@ -46,8 +46,8 @@
   /// Throw an exception if the index is not within the bounds allowed for an
   /// integer-encoded array of boolean values.
   static void _checkIndex(int index) {
-    if (index < 0 || index > 30) {
-      throw RangeError("Index not between 0 and 30: $index");
+    if (index < 0 || index > 60) {
+      throw RangeError("Index not between 0 and 60: $index");
     }
   }
 }
diff --git a/analyzer/lib/src/hint/sdk_constraint_verifier.dart b/analyzer/lib/src/hint/sdk_constraint_verifier.dart
index 07f17ad..bbd197a 100644
--- a/analyzer/lib/src/hint/sdk_constraint_verifier.dart
+++ b/analyzer/lib/src/hint/sdk_constraint_verifier.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/syntactic_entity.dart';
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -124,6 +125,20 @@
       _checkUiAsCode ??= !before_2_2_2.intersect(_versionConstraint).isEmpty;
 
   @override
+  void visitArgumentList(ArgumentList node) {
+    // Check (optional) positional arguments.
+    // Named arguments are checked in [NamedExpression].
+    for (final argument in node.arguments) {
+      if (argument is! NamedExpression) {
+        final parameter = argument.staticParameterElement;
+        _checkSinceSdkVersion(parameter, node, errorEntity: argument);
+      }
+    }
+
+    super.visitArgumentList(node);
+  }
+
+  @override
   void visitAsExpression(AsExpression node) {
     if (checkConstantUpdate2018 && node.inConstantContext) {
       _errorReporter.reportErrorForNode(
@@ -133,6 +148,13 @@
   }
 
   @override
+  void visitAssignmentExpression(AssignmentExpression node) {
+    _checkSinceSdkVersion(node.readElement, node);
+    _checkSinceSdkVersion(node.writeElement, node);
+    super.visitAssignmentExpression(node);
+  }
+
+  @override
   void visitBinaryExpression(BinaryExpression node) {
     if (checkTripleShift) {
       TokenType operatorType = node.operator.type;
@@ -172,6 +194,12 @@
   }
 
   @override
+  void visitConstructorName(ConstructorName node) {
+    _checkSinceSdkVersion(node.staticElement, node);
+    super.visitConstructorName(node);
+  }
+
+  @override
   void visitExtensionDeclaration(ExtensionDeclaration node) {
     if (checkExtensionMethods) {
       _errorReporter.reportErrorForToken(
@@ -200,6 +228,12 @@
   }
 
   @override
+  void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
+    _checkSinceSdkVersion(node.staticElement, node);
+    super.visitFunctionExpressionInvocation(node);
+  }
+
+  @override
   void visitHideCombinator(HideCombinator node) {
     // Don't flag references to either `Future` or `Stream` within a combinator.
   }
@@ -215,6 +249,12 @@
   }
 
   @override
+  void visitIndexExpression(IndexExpression node) {
+    _checkSinceSdkVersion(node.staticElement, node);
+    super.visitIndexExpression(node);
+  }
+
+  @override
   void visitIsExpression(IsExpression node) {
     if (checkConstantUpdate2018 && node.inConstantContext) {
       _errorReporter.reportErrorForNode(
@@ -233,6 +273,30 @@
   }
 
   @override
+  void visitMethodInvocation(MethodInvocation node) {
+    _checkSinceSdkVersion(node.methodName.staticElement, node);
+    super.visitMethodInvocation(node);
+  }
+
+  @override
+  void visitNamedType(NamedType node) {
+    _checkSinceSdkVersion(node.name.staticElement, node);
+    super.visitNamedType(node);
+  }
+
+  @override
+  void visitPrefixedIdentifier(PrefixedIdentifier node) {
+    _checkSinceSdkVersion(node.staticElement, node);
+    super.visitPrefixedIdentifier(node);
+  }
+
+  @override
+  void visitPropertyAccess(PropertyAccess node) {
+    _checkSinceSdkVersion(node.propertyName.staticElement, node);
+    super.visitPropertyAccess(node);
+  }
+
+  @override
   void visitSetOrMapLiteral(SetOrMapLiteral node) {
     if (node.isSet && checkSetLiterals && !_inSetLiteral) {
       _errorReporter.reportErrorForNode(
@@ -254,6 +318,7 @@
     if (node.inDeclarationContext()) {
       return;
     }
+    _checkSinceSdkVersion(node.staticElement, node);
     var element = node.staticElement;
     if (checkFutureAndStream &&
         element is InterfaceElement &&
@@ -287,6 +352,56 @@
     _inUiAsCode = wasInUiAsCode;
   }
 
+  void _checkSinceSdkVersion(
+    Element? element,
+    AstNode target, {
+    SyntacticEntity? errorEntity,
+  }) {
+    if (element != null) {
+      final sinceSdkVersion = element.sinceSdkVersion;
+      if (sinceSdkVersion != null) {
+        if (!_versionConstraint.requiresAtLeast(sinceSdkVersion)) {
+          if (errorEntity == null) {
+            if (!_shouldReportEnumIndex(target, element)) {
+              return;
+            }
+            if (target is AssignmentExpression) {
+              target = target.leftHandSide;
+            }
+            if (target is ConstructorName) {
+              errorEntity = target.name ?? target.type.name.simpleName;
+            } else if (target is FunctionExpressionInvocation) {
+              errorEntity = target.argumentList;
+            } else if (target is IndexExpression) {
+              errorEntity = target.leftBracket;
+            } else if (target is MethodInvocation) {
+              errorEntity = target.methodName;
+            } else if (target is NamedType) {
+              errorEntity = target.name.simpleName;
+            } else if (target is PrefixedIdentifier) {
+              errorEntity = target.identifier;
+            } else if (target is PropertyAccess) {
+              errorEntity = target.propertyName;
+            } else if (target is SimpleIdentifier) {
+              errorEntity = target;
+            } else {
+              throw UnimplementedError('(${target.runtimeType}) $target');
+            }
+          }
+          _errorReporter.reportErrorForOffset(
+            WarningCode.SDK_VERSION_SINCE,
+            errorEntity.offset,
+            errorEntity.length,
+            [
+              sinceSdkVersion.toString(),
+              _versionConstraint.toString(),
+            ],
+          );
+        }
+      }
+    }
+  }
+
   /// Given that the [node] is only valid when the ui-as-code feature is
   /// enabled, check that the code will not be executed with a version of the
   /// SDK that does not support the feature.
@@ -308,4 +423,48 @@
           WarningCode.SDK_VERSION_UI_AS_CODE_IN_CONST_CONTEXT, node);
     }
   }
+
+  /// Returns `false` if [element] is the `index` property, and the target
+  /// of [node] is exactly the `Enum` class from `dart:core`. We have already
+  /// checked that the property is not available to the enclosing package.
+  ///
+  /// Returns `true` if [element] is something else, or if the target is a
+  /// concrete enum. The `index` was always available for concrete enums,
+  /// but there was no common `Enum` supertype for all enums.
+  static bool _shouldReportEnumIndex(AstNode node, Element element) {
+    if (element is PropertyAccessorElement && element.name == 'index') {
+      DartType? targetType;
+      if (node is PrefixedIdentifier) {
+        targetType = node.prefix.staticType;
+      } else if (node is PropertyAccess) {
+        targetType = node.realTarget.staticType;
+      }
+      if (targetType != null) {
+        final targetElement = targetType.element;
+        return targetElement is ClassElement && targetElement.isDartCoreEnum;
+      }
+      return false;
+    } else {
+      return true;
+    }
+  }
+}
+
+extension on VersionConstraint {
+  bool requiresAtLeast(Version version) {
+    final self = this;
+    if (self is Version) {
+      return self == version;
+    }
+    if (self is VersionRange) {
+      final min = self.min;
+      if (min == null) {
+        return false;
+      } else {
+        return min >= version;
+      }
+    }
+    // We don't know, but will not complain.
+    return true;
+  }
 }
diff --git a/analyzer/lib/src/lint/linter_visitor.dart b/analyzer/lib/src/lint/linter_visitor.dart
index b027e35..feb0362 100644
--- a/analyzer/lib/src/lint/linter_visitor.dart
+++ b/analyzer/lib/src/lint/linter_visitor.dart
@@ -1798,11 +1798,6 @@
     _forRecordPattern.add(_Subscription(linter, visitor, _getTimer(linter)));
   }
 
-  @Deprecated('Use addPatternField instead')
-  void addRecordPatternField(LintRule linter, AstVisitor visitor) {
-    _forPatternField.add(_Subscription(linter, visitor, _getTimer(linter)));
-  }
-
   void addRecordTypeAnnotation(LintRule linter, AstVisitor visitor) {
     _forRecordTypeAnnotation
         .add(_Subscription(linter, visitor, _getTimer(linter)));
diff --git a/analyzer/lib/src/summary/format.dart b/analyzer/lib/src/summary/format.dart
index 96fbc9a..915183c 100644
--- a/analyzer/lib/src/summary/format.dart
+++ b/analyzer/lib/src/summary/format.dart
@@ -235,7 +235,7 @@
   }
 }
 
-abstract class _AnalysisDriverExceptionContextMixin
+mixin _AnalysisDriverExceptionContextMixin
     implements idl.AnalysisDriverExceptionContext {
   @override
   Map<String, Object> toJson() {
@@ -361,7 +361,7 @@
   }
 }
 
-abstract class _AnalysisDriverExceptionFileMixin
+mixin _AnalysisDriverExceptionFileMixin
     implements idl.AnalysisDriverExceptionFile {
   @override
   Map<String, Object> toJson() {
@@ -506,7 +506,7 @@
   }
 }
 
-abstract class _AnalysisDriverResolvedUnitMixin
+mixin _AnalysisDriverResolvedUnitMixin
     implements idl.AnalysisDriverResolvedUnit {
   @override
   Map<String, Object> toJson() {
@@ -627,8 +627,7 @@
   }
 }
 
-abstract class _AnalysisDriverSubtypeMixin
-    implements idl.AnalysisDriverSubtype {
+mixin _AnalysisDriverSubtypeMixin implements idl.AnalysisDriverSubtype {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -852,8 +851,7 @@
   }
 }
 
-abstract class _AnalysisDriverUnitErrorMixin
-    implements idl.AnalysisDriverUnitError {
+mixin _AnalysisDriverUnitErrorMixin implements idl.AnalysisDriverUnitError {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -1743,8 +1741,7 @@
   }
 }
 
-abstract class _AnalysisDriverUnitIndexMixin
-    implements idl.AnalysisDriverUnitIndex {
+mixin _AnalysisDriverUnitIndexMixin implements idl.AnalysisDriverUnitIndex {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -2536,7 +2533,7 @@
   }
 }
 
-abstract class _AvailableDeclarationMixin implements idl.AvailableDeclaration {
+mixin _AvailableDeclarationMixin implements idl.AvailableDeclaration {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -2937,7 +2934,7 @@
   }
 }
 
-abstract class _AvailableFileMixin implements idl.AvailableFile {
+mixin _AvailableFileMixin implements idl.AvailableFile {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -3093,7 +3090,7 @@
   }
 }
 
-abstract class _AvailableFileExportMixin implements idl.AvailableFileExport {
+mixin _AvailableFileExportMixin implements idl.AvailableFileExport {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -3229,7 +3226,7 @@
   }
 }
 
-abstract class _AvailableFileExportCombinatorMixin
+mixin _AvailableFileExportCombinatorMixin
     implements idl.AvailableFileExportCombinator {
   @override
   Map<String, Object> toJson() {
@@ -3340,7 +3337,7 @@
   }
 }
 
-abstract class _CiderUnitErrorsMixin implements idl.CiderUnitErrors {
+mixin _CiderUnitErrorsMixin implements idl.CiderUnitErrors {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -3518,7 +3515,7 @@
   }
 }
 
-abstract class _DiagnosticMessageMixin implements idl.DiagnosticMessage {
+mixin _DiagnosticMessageMixin implements idl.DiagnosticMessage {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
@@ -3666,7 +3663,7 @@
   }
 }
 
-abstract class _DirectiveInfoMixin implements idl.DirectiveInfo {
+mixin _DirectiveInfoMixin implements idl.DirectiveInfo {
   @override
   Map<String, Object> toJson() {
     Map<String, Object> result = <String, Object>{};
diff --git a/analyzer/lib/src/summary2/ast_binary_reader.dart b/analyzer/lib/src/summary2/ast_binary_reader.dart
index ecfc79d..403b930 100644
--- a/analyzer/lib/src/summary2/ast_binary_reader.dart
+++ b/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -18,7 +18,6 @@
 import 'package:analyzer/src/summary2/ast_binary_tag.dart';
 import 'package:analyzer/src/summary2/ast_binary_tokens.dart';
 import 'package:analyzer/src/summary2/bundle_reader.dart';
-import 'package:analyzer/src/summary2/not_serializable_nodes.dart';
 import 'package:analyzer/src/summary2/unlinked_token_type.dart';
 import 'package:collection/collection.dart';
 
@@ -83,8 +82,6 @@
         return _readFieldFormalParameter();
       case Tag.FormalParameterList:
         return _readFormalParameterList();
-      case Tag.FunctionExpressionStub:
-        return _readFunctionExpression();
       case Tag.FunctionExpressionInvocation:
         return _readFunctionExpressionInvocation();
       case Tag.FunctionReference:
@@ -139,6 +136,14 @@
         return _readPropertyAccess();
       case Tag.RecordLiteral:
         return _readRecordLiteral();
+      case Tag.RecordTypeAnnotation:
+        return _readRecordTypeAnnotation();
+      case Tag.RecordTypeAnnotationNamedField:
+        return _readRecordTypeAnnotationNamedField();
+      case Tag.RecordTypeAnnotationNamedFields:
+        return _readRecordTypeAnnotationNamedFields();
+      case Tag.RecordTypeAnnotationPositionalField:
+        return _readRecordTypeAnnotationPositionalField();
       case Tag.RedirectingConstructorInvocation:
         return _readRedirectingConstructorInvocation();
       case Tag.SetOrMapLiteral:
@@ -572,10 +577,6 @@
     );
   }
 
-  FunctionExpression _readFunctionExpression() {
-    return emptyFunctionExpression();
-  }
-
   FunctionExpressionInvocation _readFunctionExpressionInvocation() {
     var function = readNode() as ExpressionImpl;
     var typeArguments = _readOptionalNode() as TypeArgumentListImpl?;
@@ -1029,6 +1030,64 @@
     return node;
   }
 
+  RecordTypeAnnotationImpl _readRecordTypeAnnotation() {
+    final flags = _readByte();
+    final positionalFields =
+        _readNodeList<RecordTypeAnnotationPositionalFieldImpl>();
+    final namedFields =
+        _readOptionalNode() as RecordTypeAnnotationNamedFieldsImpl?;
+
+    final node = RecordTypeAnnotationImpl(
+      leftParenthesis: Tokens.openParenthesis(),
+      positionalFields: positionalFields,
+      namedFields: namedFields,
+      rightParenthesis: Tokens.closeParenthesis(),
+      question: AstBinaryFlags.hasQuestion(flags) ? Tokens.question() : null,
+    );
+    node.type = _reader.readType();
+    return node;
+  }
+
+  RecordTypeAnnotationNamedFieldImpl _readRecordTypeAnnotationNamedField() {
+    final metadata = _readNodeList<AnnotationImpl>();
+    final type = readNode() as TypeAnnotationImpl;
+
+    final lexeme = _reader.readStringReference();
+    final name = TokenFactory.tokenFromString(lexeme);
+
+    return RecordTypeAnnotationNamedFieldImpl(
+      metadata: metadata,
+      type: type,
+      name: name,
+    );
+  }
+
+  RecordTypeAnnotationNamedFieldsImpl _readRecordTypeAnnotationNamedFields() {
+    final fields = _readNodeList<RecordTypeAnnotationNamedFieldImpl>();
+    return RecordTypeAnnotationNamedFieldsImpl(
+      leftBracket: Tokens.openCurlyBracket(),
+      fields: fields,
+      rightBracket: Tokens.closeCurlyBracket(),
+    );
+  }
+
+  RecordTypeAnnotationPositionalFieldImpl
+      _readRecordTypeAnnotationPositionalField() {
+    final metadata = _readNodeList<AnnotationImpl>();
+    final type = readNode() as TypeAnnotationImpl;
+
+    final name = _reader.readOptionalObject((reader) {
+      final lexeme = reader.readStringReference();
+      return TokenFactory.tokenFromString(lexeme);
+    });
+
+    return RecordTypeAnnotationPositionalFieldImpl(
+      metadata: metadata,
+      type: type,
+      name: name,
+    );
+  }
+
   RedirectingConstructorInvocation _readRedirectingConstructorInvocation() {
     var constructorName = _readOptionalNode() as SimpleIdentifierImpl?;
     var argumentList = readNode() as ArgumentListImpl;
diff --git a/analyzer/lib/src/summary2/ast_binary_tag.dart b/analyzer/lib/src/summary2/ast_binary_tag.dart
index d2e1959..44f9f65 100644
--- a/analyzer/lib/src/summary2/ast_binary_tag.dart
+++ b/analyzer/lib/src/summary2/ast_binary_tag.dart
@@ -54,7 +54,6 @@
   static const int FormalParameterList = 17;
   static const int FunctionDeclaration_getter = 57;
   static const int FunctionDeclaration_setter = 58;
-  static const int FunctionExpressionStub = 19;
   static const int FunctionExpressionInvocation = 93;
   static const int FunctionReference = 103;
   static const int FunctionTypedFormalParameter = 20;
@@ -86,6 +85,10 @@
   static const int PrefixedIdentifier = 32;
   static const int PropertyAccess = 62;
   static const int RecordLiteral = 105;
+  static const int RecordTypeAnnotation = 106;
+  static const int RecordTypeAnnotationNamedField = 107;
+  static const int RecordTypeAnnotationNamedFields = 108;
+  static const int RecordTypeAnnotationPositionalField = 109;
   static const int RedirectingConstructorInvocation = 54;
   static const int SetOrMapLiteral = 65;
   static const int ShowCombinator = 33;
diff --git a/analyzer/lib/src/summary2/ast_binary_writer.dart b/analyzer/lib/src/summary2/ast_binary_writer.dart
index 9ddc455..ac37e52 100644
--- a/analyzer/lib/src/summary2/ast_binary_writer.dart
+++ b/analyzer/lib/src/summary2/ast_binary_writer.dart
@@ -6,8 +6,6 @@
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/type.dart';
-import 'package:analyzer/src/dart/ast/ast_factory.dart';
-import 'package:analyzer/src/dart/ast/token.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/summary2/ast_binary_flags.dart';
 import 'package:analyzer/src/summary2/ast_binary_tag.dart';
@@ -265,11 +263,6 @@
   }
 
   @override
-  void visitForElement(ForElement node) {
-    _writeNotSerializableExpression();
-  }
-
-  @override
   void visitFormalParameterList(FormalParameterList node) {
     _writeByte(Tag.FormalParameterList);
 
@@ -299,11 +292,6 @@
   }
 
   @override
-  void visitFunctionExpression(FunctionExpression node) {
-    _writeByte(Tag.FunctionExpressionStub);
-  }
-
-  @override
   void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
     _writeByte(Tag.FunctionExpressionInvocation);
 
@@ -640,6 +628,52 @@
   }
 
   @override
+  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
+    _writeByte(Tag.RecordTypeAnnotation);
+
+    _writeByte(
+      AstBinaryFlags.encode(
+        hasQuestion: node.question != null,
+      ),
+    );
+
+    _writeNodeList(node.positionalFields);
+    _writeOptionalNode(node.namedFields);
+
+    _sink.writeType(node.type);
+  }
+
+  @override
+  void visitRecordTypeAnnotationNamedField(
+    RecordTypeAnnotationNamedField node,
+  ) {
+    _writeByte(Tag.RecordTypeAnnotationNamedField);
+    _writeNodeList(node.metadata);
+    _writeNode(node.type);
+    _writeStringReference(node.name.lexeme);
+  }
+
+  @override
+  void visitRecordTypeAnnotationNamedFields(
+    RecordTypeAnnotationNamedFields node,
+  ) {
+    _writeByte(Tag.RecordTypeAnnotationNamedFields);
+    _writeNodeList(node.fields);
+  }
+
+  @override
+  void visitRecordTypeAnnotationPositionalField(
+    RecordTypeAnnotationPositionalField node,
+  ) {
+    _writeByte(Tag.RecordTypeAnnotationPositionalField);
+    _writeNodeList(node.metadata);
+    _writeNode(node.type);
+    _sink.writeOptionalObject(node.name, (name) {
+      _writeStringReference(name.lexeme);
+    });
+  }
+
+  @override
   void visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     _writeByte(Tag.RedirectingConstructorInvocation);
@@ -906,13 +940,6 @@
     }
   }
 
-  void _writeNotSerializableExpression() {
-    var node = astFactory.simpleIdentifier(
-      StringToken(TokenType.STRING, '_notSerializableExpression', -1),
-    );
-    node.accept(this);
-  }
-
   void _writeOptionalNode(AstNode? node) {
     if (node == null) {
       _writeByte(Tag.Nothing);
diff --git a/analyzer/lib/src/summary2/bundle_reader.dart b/analyzer/lib/src/summary2/bundle_reader.dart
index 48d7727..60dbffd 100644
--- a/analyzer/lib/src/summary2/bundle_reader.dart
+++ b/analyzer/lib/src/summary2/bundle_reader.dart
@@ -1651,6 +1651,10 @@
     return type is FunctionType ? type : null;
   }
 
+  T? readOptionalObject<T>(T Function(SummaryDataReader reader) read) {
+    return _reader.readOptionalObject(read);
+  }
+
   List<DartType>? readOptionalTypeList() {
     if (_reader.readBool()) {
       return _readTypeList();
@@ -1847,7 +1851,7 @@
     required CompilationUnitElementImpl unitElement,
   }) {
     return readTypedList(() {
-      var ast = _readRequiredNode() as Annotation;
+      var ast = _readRequiredNode() as AnnotationImpl;
       return ElementAnnotationImpl(unitElement)
         ..annotationAst = ast
         ..element = ast.element;
diff --git a/analyzer/lib/src/summary2/default_types_builder.dart b/analyzer/lib/src/summary2/default_types_builder.dart
index d37b32d..c74e8d8 100644
--- a/analyzer/lib/src/summary2/default_types_builder.dart
+++ b/analyzer/lib/src/summary2/default_types_builder.dart
@@ -54,6 +54,16 @@
         _breakSelfCycles(node.typeParameters);
         _breakRawTypeCycles(element, node.typeParameters);
         _computeBounds(element, node.typeParameters);
+      } else if (node is MethodDeclaration) {
+        var element = node.declaredElement!;
+        _breakSelfCycles(node.typeParameters);
+        _breakRawTypeCycles(element, node.typeParameters);
+        _computeBounds(element, node.typeParameters);
+      } else if (node is FunctionDeclaration) {
+        var element = node.declaredElement!;
+        _breakSelfCycles(node.functionExpression.typeParameters);
+        _breakRawTypeCycles(element, node.functionExpression.typeParameters);
+        _computeBounds(element, node.functionExpression.typeParameters);
       }
     }
     for (var node in nodes) {
@@ -69,6 +79,10 @@
         _build(node.typeParameters);
       } else if (node is MixinDeclaration) {
         _build(node.typeParameters);
+      } else if (node is FunctionDeclaration) {
+        _build(node.functionExpression.typeParameters);
+      } else if (node is MethodDeclaration) {
+        _build(node.typeParameters);
       }
     }
   }
diff --git a/analyzer/lib/src/summary2/detach_nodes.dart b/analyzer/lib/src/summary2/detach_nodes.dart
index 7f069ca..b6213cc 100644
--- a/analyzer/lib/src/summary2/detach_nodes.dart
+++ b/analyzer/lib/src/summary2/detach_nodes.dart
@@ -33,6 +33,15 @@
       // Make a copy, so that it is not a NodeList.
       var initializers = element.constantInitializers.toFixedList();
       initializers.forEach(_detachNode);
+
+      for (final initializer in initializers) {
+        if (initializer is ConstructorFieldInitializerImpl) {
+          final expression = initializer.expression;
+          final replacement = replaceNotSerializableNode(expression);
+          initializer.expression = replacement;
+        }
+      }
+
       element.constantInitializers = initializers;
     }
     super.visitConstructorElement(element);
@@ -40,8 +49,16 @@
 
   @override
   void visitElement(Element element) {
-    for (var elementAnnotation in element.metadata) {
-      _detachNode((elementAnnotation as ElementAnnotationImpl).annotationAst);
+    for (final annotation in element.metadata) {
+      final ast = (annotation as ElementAnnotationImpl).annotationAst;
+      _detachNode(ast);
+      // Sanitize arguments.
+      final arguments = ast.arguments?.arguments;
+      if (arguments != null) {
+        for (var i = 0; i < arguments.length; i++) {
+          arguments[i] = replaceNotSerializableNode(arguments[i]);
+        }
+      }
     }
     super.visitElement(element);
   }
@@ -75,7 +92,7 @@
       if (initializer is ExpressionImpl) {
         _detachNode(initializer);
 
-        initializer = replaceNotSerializableNodes(initializer);
+        initializer = replaceNotSerializableNode(initializer);
         element.constantInitializer = initializer;
 
         ConstantContextForExpressionImpl(element, initializer);
diff --git a/analyzer/lib/src/summary2/informative_data.dart b/analyzer/lib/src/summary2/informative_data.dart
index 753dd26..7692835 100644
--- a/analyzer/lib/src/summary2/informative_data.dart
+++ b/analyzer/lib/src/summary2/informative_data.dart
@@ -15,6 +15,7 @@
 import 'package:analyzer/src/summary2/data_reader.dart';
 import 'package:analyzer/src/summary2/data_writer.dart';
 import 'package:analyzer/src/summary2/linked_element_factory.dart';
+import 'package:analyzer/src/summary2/not_serializable_nodes.dart';
 import 'package:analyzer/src/util/collection.dart';
 import 'package:analyzer/src/util/comment.dart';
 import 'package:collection/collection.dart';
@@ -1803,6 +1804,15 @@
     }
   }
 
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    if (isNotSerializableMarker(node)) {
+      return;
+    }
+
+    super.visitSimpleIdentifier(node);
+  }
+
   void _applyToEnumConstantInitializer(ConstFieldElementImpl element) {
     var initializer = element.constantInitializer;
     if (initializer is InstanceCreationExpression) {
@@ -2032,6 +2042,39 @@
   }
 
   @override
+  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
+    _tokenOrNull(node.leftParenthesis);
+    _tokenOrNull(node.rightParenthesis);
+    _tokenOrNull(node.question);
+    super.visitRecordTypeAnnotation(node);
+  }
+
+  @override
+  void visitRecordTypeAnnotationNamedField(
+    RecordTypeAnnotationNamedField node,
+  ) {
+    _tokenOrNull(node.name);
+    super.visitRecordTypeAnnotationNamedField(node);
+  }
+
+  @override
+  void visitRecordTypeAnnotationNamedFields(
+    RecordTypeAnnotationNamedFields node,
+  ) {
+    _tokenOrNull(node.leftBracket);
+    _tokenOrNull(node.rightBracket);
+    super.visitRecordTypeAnnotationNamedFields(node);
+  }
+
+  @override
+  void visitRecordTypeAnnotationPositionalField(
+    RecordTypeAnnotationPositionalField node,
+  ) {
+    _tokenOrNull(node.name);
+    super.visitRecordTypeAnnotationPositionalField(node);
+  }
+
+  @override
   void visitRedirectingConstructorInvocation(
       RedirectingConstructorInvocation node) {
     _tokenOrNull(node.thisKeyword);
diff --git a/analyzer/lib/src/summary2/linked_element_factory.dart b/analyzer/lib/src/summary2/linked_element_factory.dart
index aed4049..da87dca 100644
--- a/analyzer/lib/src/summary2/linked_element_factory.dart
+++ b/analyzer/lib/src/summary2/linked_element_factory.dart
@@ -114,17 +114,24 @@
     var reader = _libraryReaders[uri];
     if (reader == null) {
       var rootChildren = rootReference.children.map((e) => e.name).toList();
-      if (rootChildren.length > 100) {
+      if (rootChildren.length > 50) {
         rootChildren = [
-          ...rootChildren.take(100),
-          '... (${rootChildren.length} total)'
+          ...rootChildren.take(50),
+          '... (${rootChildren.length} total)',
+        ];
+      }
+      var readers = _libraryReaders.keys.map((uri) => uri.toString()).toList();
+      if (readers.length > 50) {
+        readers = [
+          ...readers.take(50),
+          '... (${readers.length} total)',
         ];
       }
       throw ArgumentError(
         'Missing library: $uri\n'
         'Libraries: $uriListWithLibraryElements\n'
         'Root children: $rootChildren\n'
-        'Readers: ${_libraryReaders.keys.toList()}\n'
+        'Readers: $readers\n'
         'Log: ${_logRing.join('\n')}\n',
       );
     }
diff --git a/analyzer/lib/src/summary2/macro_declarations.dart b/analyzer/lib/src/summary2/macro_declarations.dart
index 2a123a3..8c531e9 100644
--- a/analyzer/lib/src/summary2/macro_declarations.dart
+++ b/analyzer/lib/src/summary2/macro_declarations.dart
@@ -20,8 +20,13 @@
     required super.identifier,
     required super.typeParameters,
     required super.interfaces,
-    required super.isAbstract,
-    required super.isExternal,
+    required super.hasAbstract,
+    required super.hasBase,
+    required super.hasExternal,
+    required super.hasFinal,
+    required super.hasInterface,
+    required super.hasMixin,
+    required super.hasSealed,
     required super.mixins,
     required super.superclass,
   });
@@ -140,8 +145,13 @@
           .map(_dartType)
           .cast<macro.NamedTypeAnnotationImpl>()
           .toList(),
-      isAbstract: element.isAbstract,
-      isExternal: false,
+      hasAbstract: element.isAbstract,
+      hasBase: element.isBase,
+      hasExternal: false,
+      hasFinal: element.isFinal,
+      hasInterface: element.isInterface,
+      hasMixin: element.isMixinClass,
+      hasSealed: element.isSealed,
       mixins: element.mixins
           .map(_dartType)
           .cast<macro.NamedTypeAnnotationImpl>()
@@ -217,8 +227,13 @@
       identifier: _declaredIdentifier(node.name, node.declaredElement!),
       typeParameters: _typeParameters(node.typeParameters),
       interfaces: _typeAnnotations(node.implementsClause?.interfaces),
-      isAbstract: node.abstractKeyword != null,
-      isExternal: false,
+      hasAbstract: node.abstractKeyword != null,
+      hasBase: node.baseKeyword != null,
+      hasExternal: false,
+      hasFinal: node.finalKeyword != null,
+      hasInterface: node.interfaceKeyword != null,
+      hasMixin: node.mixinKeyword != null,
+      hasSealed: node.sealedKeyword != null,
       mixins: _typeAnnotations(node.withClause?.mixinTypes),
       superclass: node.extendsClause?.superclass.mapOrNull(
         _typeAnnotation,
@@ -348,8 +363,13 @@
     required super.identifier,
     required super.typeParameters,
     required super.interfaces,
-    required super.isAbstract,
-    required super.isExternal,
+    required super.hasAbstract,
+    required super.hasBase,
+    required super.hasFinal,
+    required super.hasExternal,
+    required super.hasInterface,
+    required super.hasMixin,
+    required super.hasSealed,
     required super.mixins,
     required super.superclass,
   });
diff --git a/analyzer/lib/src/summary2/not_serializable_nodes.dart b/analyzer/lib/src/summary2/not_serializable_nodes.dart
index f9598fd..b98e883 100644
--- a/analyzer/lib/src/summary2/not_serializable_nodes.dart
+++ b/analyzer/lib/src/summary2/not_serializable_nodes.dart
@@ -3,59 +3,46 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
-import 'package:analyzer/src/dart/ast/utilities.dart';
-import 'package:analyzer/src/summary2/ast_binary_tokens.dart';
+import 'package:analyzer/src/dart/ast/ast_factory.dart';
+import 'package:analyzer/src/dart/ast/token.dart';
 
-FunctionExpressionImpl emptyFunctionExpression() {
-  return FunctionExpressionImpl(
-    typeParameters: null,
-    parameters: FormalParameterListImpl(
-      leftParenthesis: Tokens.openParenthesis(),
-      parameters: [],
-      leftDelimiter: null,
-      rightDelimiter: null,
-      rightParenthesis: Tokens.closeParenthesis(),
-    ),
-    body: BlockFunctionBodyImpl(
-      keyword: null,
-      star: null,
-      block: BlockImpl(
-        leftBracket: Tokens.openCurlyBracket(),
-        statements: [],
-        rightBracket: Tokens.closeCurlyBracket(),
-      ),
-    ),
+const _notSerializableName = '_notSerializableExpression';
+
+bool isNotSerializableMarker(SimpleIdentifier node) {
+  return node.name == _notSerializableName;
+}
+
+/// If [node] is fully serializable, returns it.
+/// Otherwise returns a marker node.
+ExpressionImpl replaceNotSerializableNode(ExpressionImpl node) {
+  final visitor = _IsSerializableNodeVisitor();
+  node.accept(visitor);
+  if (visitor.result) {
+    return node;
+  }
+  return astFactory.simpleIdentifier(
+    StringToken(TokenType.STRING, _notSerializableName, -1),
   );
 }
 
-/// We cannot serialize [FunctionExpression], but we need to have some node
-/// with the same source range for error reporting. So, we replace them with
-/// empty [FunctionExpression]s that have the same offset and length.
-ExpressionImpl replaceNotSerializableNodes(ExpressionImpl node) {
-  if (node is FunctionExpressionImpl) {
-    return FunctionExpressionReplacementVisitor._replacement(node);
-  }
-  node.accept(FunctionExpressionReplacementVisitor());
-  return node;
-}
+class _IsSerializableNodeVisitor extends RecursiveAstVisitor<void> {
+  bool result = true;
 
-class FunctionExpressionReplacementVisitor extends RecursiveAstVisitor<void> {
+  @override
+  void visitForElement(ForElement node) {
+    result = false;
+  }
+
   @override
   void visitFunctionExpression(FunctionExpression node) {
-    NodeReplacer.replace(node, _replacement(node));
+    result = false;
   }
 
-  static FunctionExpressionImpl _replacement(FunctionExpression from) {
-    var to = emptyFunctionExpression();
-    to.parameters?.leftParenthesis.offset = from.offset;
-
-    var toBody = to.body;
-    if (toBody is BlockFunctionBodyImpl) {
-      toBody.block.rightBracket.offset = from.end - 1;
-    }
-
-    return to;
+  @override
+  void visitSwitchExpression(SwitchExpression node) {
+    result = false;
   }
 }
diff --git a/analyzer/lib/src/summary2/top_level_inference.dart b/analyzer/lib/src/summary2/top_level_inference.dart
index db1d0e3..1d1040f 100644
--- a/analyzer/lib/src/summary2/top_level_inference.dart
+++ b/analyzer/lib/src/summary2/top_level_inference.dart
@@ -244,7 +244,7 @@
 
     final enclosingElement = _element.enclosingElement;
     final enclosingClassElement =
-        enclosingElement is ClassElement ? enclosingElement : null;
+        enclosingElement is InterfaceElement ? enclosingElement : null;
 
     var astResolver = AstResolver(_linker, _unitElement, _scope,
         enclosingClassElement: enclosingClassElement);
diff --git a/analyzer/lib/src/summary2/types_builder.dart b/analyzer/lib/src/summary2/types_builder.dart
index 41d6d93..9d464b0 100644
--- a/analyzer/lib/src/summary2/types_builder.dart
+++ b/analyzer/lib/src/summary2/types_builder.dart
@@ -20,7 +20,7 @@
 
 /// Return `true` if [type] can be used as a class.
 bool _isInterfaceTypeClass(InterfaceType type) {
-  if (type.element is MixinElement) {
+  if (type.element is! ClassElement) {
     return false;
   }
   return _isInterfaceTypeInterface(type);
@@ -147,7 +147,7 @@
     var element = node.declaredElement as ClassElementImpl;
 
     var superType = node.superclass.type;
-    if (superType is InterfaceType && _isInterfaceTypeInterface(superType)) {
+    if (superType is InterfaceType && _isInterfaceTypeClass(superType)) {
       element.supertype = superType;
     } else {
       element.supertype = _objectType(element);
diff --git a/analyzer/lib/src/task/strong/checker.dart b/analyzer/lib/src/task/strong/checker.dart
index 2511b49..98ba14b 100644
--- a/analyzer/lib/src/task/strong/checker.dart
+++ b/analyzer/lib/src/task/strong/checker.dart
@@ -649,7 +649,7 @@
   bool? _needsImplicitCast(SyntacticEntity expr,
       {required DartType from, required DartType to}) {
     // Void is considered Top, but may only be *explicitly* cast.
-    if (from.isVoid) return null;
+    if (from is VoidType) return null;
 
     if (to is FunctionType) {
       var needsCast = _checkFunctionTypeCasts(to, from);
diff --git a/analyzer/lib/src/task/strong_mode.dart b/analyzer/lib/src/task/strong_mode.dart
index 7f90774..fed4320 100644
--- a/analyzer/lib/src/task/strong_mode.dart
+++ b/analyzer/lib/src/task/strong_mode.dart
@@ -303,6 +303,7 @@
   /// Infer type information for all of the instance members in the given
   /// [classElement].
   void _inferClass(InterfaceElement classElement) {
+    _setInducedModifier(classElement);
     if (classElement is AbstractClassElementImpl) {
       if (classElement.hasBeenInferred) {
         return;
@@ -605,6 +606,87 @@
     element.isOperatorEqualWithParameterTypeFromObject = true;
   }
 
+  /// Find and mark the induced modifier of an element, if the [classElement] is
+  /// 'sealed'.
+  void _setInducedModifier(InterfaceElement classElement) {
+    // Only sealed elements propagate induced modifiers.
+    if (classElement is! ClassOrMixinElementImpl || !classElement.isSealed) {
+      return;
+    }
+
+    final supertype = classElement.supertype;
+    final interfaces = classElement.interfaces;
+    final mixins = classElement.mixins;
+
+    if (mixins.any((type) => type.element.isFinal)) {
+      // A sealed declaration is considered 'final' if it has a direct
+      // superclass which is 'final'.
+      classElement.isFinal = true;
+      return;
+    }
+
+    if (supertype != null) {
+      if (supertype.element.isFinal) {
+        // A sealed declaration is considered 'final' if it has a direct
+        // superclass which is 'final'.
+        classElement.isFinal = true;
+        return;
+      }
+      if (supertype.element.isBase) {
+        // A sealed declaration is considered 'final' if it has a
+        // direct superclass which is 'interface' and it has a direct
+        // superinterface which is 'base'.
+        if (mixins.any((type) => type.element.isInterface)) {
+          classElement.isFinal = true;
+          return;
+        }
+
+        // Otherwise, a sealed declaration is considered 'base' if it has a
+        // direct superinterface which is 'base' or 'final'.
+        classElement.isBase = true;
+        return;
+      }
+      if (supertype.element.isInterface) {
+        // A sealed declaration is considered 'final' if it has a
+        // direct superclass which is 'interface' and it has a direct
+        // superinterface which is 'base'.
+        if (interfaces.any((type) => type.element.isBase) ||
+            mixins.any((type) => type.element.isBase)) {
+          classElement.isFinal = true;
+          return;
+        }
+
+        // Otherwise, a sealed declaration is considered 'interface' if it has a
+        // direct superclass which is 'interface'
+        classElement.isInterface = true;
+        return;
+      }
+    }
+
+    if (interfaces.any((type) => type.element.isBase || type.element.isFinal) ||
+        mixins.any((type) => type.element.isBase || type.element.isFinal)) {
+      // A sealed declaration is considered 'base' if it has a direct
+      // superinterface which is 'base' or 'final'.
+      classElement.isBase = true;
+      return;
+    }
+
+    if (mixins.any((type) => type.element.isInterface)) {
+      // A sealed declaration is considered 'final' if it has a
+      // direct superclass which is 'interface' and it has a direct
+      // superinterface which is 'base'.
+      if (interfaces.any((type) => type.element.isBase)) {
+        classElement.isFinal = true;
+        return;
+      }
+
+      // Otherwise, a sealed declaration is considered 'interface' if it has a
+      // direct superclass which is 'interface'
+      classElement.isInterface = true;
+      return;
+    }
+  }
+
   /// Return the [FunctionType] of the [overriddenElement] that [element]
   /// overrides. Return `null`, in case of type parameters inconsistency.
   ///
@@ -647,3 +729,23 @@
 
 /// A class of exception that is not used anywhere else.
 class _CycleException implements Exception {}
+
+extension on InterfaceElement {
+  bool get isBase {
+    var self = this;
+    if (self is ClassOrMixinElementImpl) return self.isBase;
+    return false;
+  }
+
+  bool get isFinal {
+    var self = this;
+    if (self is ClassOrMixinElementImpl) return self.isFinal;
+    return false;
+  }
+
+  bool get isInterface {
+    var self = this;
+    if (self is ClassOrMixinElementImpl) return self.isInterface;
+    return false;
+  }
+}
diff --git a/analyzer/lib/src/test_utilities/find_element.dart b/analyzer/lib/src/test_utilities/find_element.dart
index 6db2857..1df6bfb 100644
--- a/analyzer/lib/src/test_utilities/find_element.dart
+++ b/analyzer/lib/src/test_utilities/find_element.dart
@@ -256,52 +256,17 @@
   TypeParameterElement typeParameter(String name) {
     TypeParameterElement? result;
 
-    void findIn(List<TypeParameterElement> typeParameters) {
-      for (var typeParameter in typeParameters) {
-        if (typeParameter.name == name) {
+    unit.accept(FunctionAstVisitor(
+      typeParameter: (node) {
+        final element = node.declaredElement!;
+        if (element.name == name) {
           if (result != null) {
             throw StateError('Not unique: $name');
           }
-          result = typeParameter;
+          result = element;
         }
-      }
-    }
-
-    void findInClass(InterfaceElement class_) {
-      findIn(class_.typeParameters);
-      for (var method in class_.methods) {
-        findIn(method.typeParameters);
-      }
-    }
-
-    for (var type in unitElement.functions) {
-      findIn(type.typeParameters);
-    }
-
-    for (var alias in unitElement.typeAliases) {
-      findIn(alias.typeParameters);
-
-      var aliasedElement = alias.aliasedElement;
-      if (aliasedElement is GenericFunctionTypeElement) {
-        findIn(aliasedElement.typeParameters);
-      }
-    }
-
-    for (var class_ in unitElement.classes) {
-      findInClass(class_);
-    }
-
-    for (var enum_ in unitElement.enums) {
-      findInClass(enum_);
-    }
-
-    for (var extension_ in unitElement.extensions) {
-      findIn(extension_.typeParameters);
-    }
-
-    for (var mixin in unitElement.mixins) {
-      findInClass(mixin);
-    }
+      },
+    ));
 
     if (result != null) {
       return result!;
diff --git a/analyzer/lib/src/test_utilities/find_node.dart b/analyzer/lib/src/test_utilities/find_node.dart
index 131d532..83f3a90 100644
--- a/analyzer/lib/src/test_utilities/find_node.dart
+++ b/analyzer/lib/src/test_utilities/find_node.dart
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/test_utilities/function_ast_visitor.dart';
 
@@ -12,11 +14,6 @@
 
   FindNode(this.content, this.unit);
 
-  LibraryDirective get libraryDirective {
-    return unit.directives.singleWhere((d) => d is LibraryDirective)
-        as LibraryDirective;
-  }
-
   List<MethodInvocation> get methodInvocations {
     var result = <MethodInvocation>[];
     unit.accept(
@@ -27,117 +24,32 @@
     return result;
   }
 
-  /// Returns the [ForElement], there must be only one.
-  ForElement get singleForElement {
-    var nodes = <ForElement>[];
-    unit.accept(
-      FunctionAstVisitor(
-        forElement: (node) {
-          nodes.add(node);
-        },
-      ),
-    );
-    return nodes.single;
-  }
+  Block get singleBlock => _single();
 
-  /// Returns the [ForStatement], there must be only one.
-  ForStatement get singleForStatement {
-    var nodes = <ForStatement>[];
-    unit.accept(
-      FunctionAstVisitor(
-        forStatement: (node) {
-          nodes.add(node);
-        },
-      ),
-    );
-    return nodes.single;
-  }
+  ForElement get singleForElement => _single();
 
-  /// Returns the [GuardedPattern], there must be only one.
-  GuardedPattern get singleGuardedPattern {
-    var nodes = <GuardedPattern>[];
-    unit.accept(
-      FunctionAstVisitor(
-        guardedPattern: (node) {
-          nodes.add(node);
-        },
-      ),
-    );
-    return nodes.single;
-  }
+  ForStatement get singleForStatement => _single();
 
-  /// Returns the [IfElement], there must be only one.
-  IfElement get singleIfElement {
-    var nodes = <IfElement>[];
-    unit.accept(
-      FunctionAstVisitor(
-        ifElement: (node) {
-          nodes.add(node);
-        },
-      ),
-    );
-    return nodes.single;
-  }
+  FunctionBody get singleFunctionBody => _single();
 
-  /// Returns the [IfStatement], there must be only one.
-  IfStatement get singleIfStatement {
-    var nodes = <IfStatement>[];
-    unit.accept(
-      FunctionAstVisitor(
-        ifStatement: (node) {
-          nodes.add(node);
-        },
-      ),
-    );
-    return nodes.single;
-  }
+  GuardedPattern get singleGuardedPattern => _single();
 
-  /// Returns the [PatternAssignment], there must be only one.
-  PatternAssignment get singlePatternAssignment {
-    var nodes = <PatternAssignment>[];
-    unit.accept(
-      FunctionAstVisitor(
-        patternAssignment: nodes.add,
-      ),
-    );
-    return nodes.single;
-  }
+  IfElement get singleIfElement => _single();
 
-  /// Returns the [PatternVariableDeclaration], there must be only one.
-  PatternVariableDeclaration get singlePatternVariableDeclaration {
-    var nodes = <PatternVariableDeclaration>[];
-    unit.accept(
-      FunctionAstVisitor(
-        patternVariableDeclaration: nodes.add,
-      ),
-    );
-    return nodes.single;
-  }
+  IfStatement get singleIfStatement => _single();
 
-  /// Returns the [PatternVariableDeclarationStatement], there must be only one.
+  LibraryDirective get singleLibraryDirective => _single();
+
+  PatternAssignment get singlePatternAssignment => _single();
+
+  PatternVariableDeclaration get singlePatternVariableDeclaration => _single();
+
   PatternVariableDeclarationStatement
-      get singlePatternVariableDeclarationStatement {
-    var nodes = <PatternVariableDeclarationStatement>[];
-    unit.accept(
-      FunctionAstVisitor(
-        patternVariableDeclarationStatement: nodes.add,
-      ),
-    );
-    return nodes.single;
-  }
+      get singlePatternVariableDeclarationStatement => _single();
 
-  /// Returns the [SwitchExpression], there must be only one.
-  SwitchExpression get singleSwitchExpression {
-    var nodes = <SwitchExpression>[];
-    unit.accept(
-      FunctionAstVisitor(
-        switchExpression: (node) {
-          nodes.add(node);
-        },
-      ),
-    );
-    return nodes.single;
-  }
+  SwitchExpression get singleSwitchExpression => _single();
+
+  SwitchPatternCase get singleSwitchPatternCase => _single();
 
   AdjacentStrings adjacentStrings(String search) {
     return _node(search, (n) => n is AdjacentStrings);
@@ -187,6 +99,11 @@
     return _node(search, (n) => n is BinaryExpression);
   }
 
+  BindPatternVariableElement bindPatternVariableElement(String search) {
+    final node = declaredVariablePattern(search);
+    return node.declaredElement!;
+  }
+
   Block block(String search) {
     return _node(search, (n) => n is Block);
   }
@@ -857,4 +774,25 @@
     }
     return result as T;
   }
+
+  /// If [unit] has exactly one node of type [T], returns it.
+  /// Otherwise, throws.
+  T _single<T extends AstNode>() {
+    final visitor = _TypedNodeVisitor<T>();
+    unit.accept(visitor);
+    return visitor.nodes.single;
+  }
+}
+
+class _TypedNodeVisitor<T extends AstNode>
+    extends GeneralizingAstVisitor<void> {
+  final List<T> nodes = [];
+
+  @override
+  void visitNode(AstNode node) {
+    if (node is T) {
+      nodes.add(node);
+    }
+    super.visitNode(node);
+  }
 }
diff --git a/analyzer/lib/src/test_utilities/function_ast_visitor.dart b/analyzer/lib/src/test_utilities/function_ast_visitor.dart
index 99eebbe..37f21b7 100644
--- a/analyzer/lib/src/test_utilities/function_ast_visitor.dart
+++ b/analyzer/lib/src/test_utilities/function_ast_visitor.dart
@@ -7,6 +7,7 @@
 
 /// [RecursiveAstVisitor] that delegates visit methods to functions.
 class FunctionAstVisitor extends RecursiveAstVisitor<void> {
+  final void Function(Block)? block;
   final void Function(CatchClauseParameter)? catchClauseParameter;
   final void Function(DeclaredIdentifier)? declaredIdentifier;
   final void Function(DeclaredVariablePattern)? declaredVariablePattern;
@@ -28,9 +29,11 @@
   final void Function(SwitchExpression)? switchExpression;
   final void Function(SwitchExpressionCase)? switchExpressionCase;
   final void Function(SwitchPatternCase)? switchPatternCase;
+  final void Function(TypeParameter)? typeParameter;
   final void Function(VariableDeclaration)? variableDeclaration;
 
   FunctionAstVisitor({
+    this.block,
     this.catchClauseParameter,
     this.declaredIdentifier,
     this.declaredVariablePattern,
@@ -50,10 +53,17 @@
     this.switchExpression,
     this.switchExpressionCase,
     this.switchPatternCase,
+    this.typeParameter,
     this.variableDeclaration,
   });
 
   @override
+  void visitBlock(Block node) {
+    block?.call(node);
+    super.visitBlock(node);
+  }
+
+  @override
   void visitCatchClauseParameter(CatchClauseParameter node) {
     catchClauseParameter?.call(node);
     super.visitCatchClauseParameter(node);
@@ -187,6 +197,12 @@
   }
 
   @override
+  void visitTypeParameter(TypeParameter node) {
+    typeParameter?.call(node);
+    super.visitTypeParameter(node);
+  }
+
+  @override
   void visitVariableDeclaration(VariableDeclaration node) {
     if (variableDeclaration != null) {
       variableDeclaration!(node);
diff --git a/analyzer/lib/src/test_utilities/mock_packages.dart b/analyzer/lib/src/test_utilities/mock_packages.dart
index 6dd9139..abe0097 100644
--- a/analyzer/lib/src/test_utilities/mock_packages.dart
+++ b/analyzer/lib/src/test_utilities/mock_packages.dart
@@ -20,7 +20,7 @@
   void free(Pointer pointer);
 }
 
-class Utf8 extends Opaque {}
+final class Utf8 extends Opaque {}
 
 class _CallocAllocator implements Allocator {
   @override
diff --git a/analyzer/lib/src/test_utilities/mock_sdk.dart b/analyzer/lib/src/test_utilities/mock_sdk.dart
index 8a97b2b..06588b5 100644
--- a/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -16,6 +16,7 @@
     '''
 library dart.async;
 
+import 'dart:_internal' show Since;
 import 'dart:math';
 
 part 'stream.dart';
@@ -72,6 +73,7 @@
   static void run(void callback()) {}
 }
 
+@Since("2.15")
 void unawaited(Future<void>? future) {}
 ''',
   ),
@@ -86,6 +88,7 @@
     throw 0;
   }
 
+  @Since("2.5")
   factory Stream.value(T value) {
     throw 0;
   }
@@ -159,12 +162,13 @@
     throw 0;
   }
 
+  @Since("2.1")
   factory HashMap.fromEntries(Iterable<MapEntry<K, V>> entries) {
     throw 0;
   }
 }
 
-abstract class IterableMixin<E> implements Iterable<E> { }
+abstract mixin class IterableMixin<E> implements Iterable<E> { }
 
 abstract class LinkedHashMap<K, V> implements Map<K, V> {
   external factory LinkedHashMap(
@@ -191,6 +195,7 @@
     throw 0;
   }
 
+  @Since("2.1")
   factory LinkedHashMap.fromEntries(Iterable<MapEntry<K, V>> entries) {
     throw 0;
   }
@@ -213,11 +218,11 @@
   }
 }
 
-abstract class ListMixin<E> implements List<E> { }
+abstract mixin class ListMixin<E> implements List<E> { }
 
-abstract class MapMixin<K, V> implements Map<K, V> { }
+abstract mixin class MapMixin<K, V> implements Map<K, V> { }
 
-abstract class SetMixin<E> implements Set<E> { }
+abstract mixin class SetMixin<E> implements Set<E> { }
 
 abstract class Queue<E> implements Iterable<E> {
   bool remove(Object? value);
@@ -251,7 +256,7 @@
 
 abstract class StringConversionSink { }
 
-abstract class StringConversionSinkMixin implements StringConversionSink { }
+abstract mixin class StringConversionSinkMixin implements StringConversionSink { }
 ''',
     )
   ],
@@ -268,6 +273,7 @@
 import "dart:_internal" hide Symbol;
 import "dart:_internal" as internal show Symbol;
 
+@Since("2.1")
 export 'dart:async' show Future, Stream;
 
 const deprecated = const Deprecated("next release");
@@ -281,6 +287,7 @@
 class ArgumentError extends Error {
   ArgumentError([message]);
 
+  @Since("2.1")
   static T checkNotNull<T>(T argument, [String, name]) => argument;
 }
 
@@ -290,14 +297,19 @@
   static BigInt parse(String source, {int? radix}) => throw 0;
 }
 
-abstract class bool extends Object {
+abstract final class bool extends Object {
   external const factory bool.fromEnvironment(String name,
       {bool defaultValue = false});
 
   external const factory bool.hasEnvironment(String name);
 
+  @Since("2.1")
   bool operator &(bool other);
+
+  @Since("2.1")
   bool operator |(bool other);
+
+  @Since("2.1")
   bool operator ^(bool other);
 }
 
@@ -326,7 +338,7 @@
   const pragma(this.name, [this.options]);
 }
 
-abstract class double extends num {
+abstract final class double extends num {
   static const double nan = 0.0 / 0.0;
   static const double infinity = 1.0 / 0.0;
   static const double negativeInfinity = -infinity;
@@ -364,6 +376,7 @@
   int compareTo(Duration other) => 0;
 }
 
+@Since("2.14")
 abstract class Enum {
   int get index; // Enum
   String get _name;
@@ -389,9 +402,9 @@
 
 class FormatException implements Exception {}
 
-class Function {}
+abstract final class Function {}
 
-abstract class int extends num {
+abstract final class int extends num {
   external const factory int.fromEnvironment(String name,
       {int defaultValue = 0});
 
@@ -465,7 +478,10 @@
 
 class List<E> implements Iterable<E> {
   external factory List.filled(int length, E fill, {bool growable = false});
+
+  @Since("2.9")
   external factory List.empty({bool growable = false});
+
   external factory List.from(Iterable elements, {bool growable = true});
   external factory List.of(Iterable<E> elements, {bool growable = true});
   external factory List.generate(int length, E generator(int index),
@@ -518,7 +534,7 @@
   V? remove(Object? key);
 }
 
-class Null extends Object {
+final class Null extends Object {
   factory Null._uninstantiable() {
     throw 0;
   }
@@ -531,7 +547,7 @@
   const MapEntry._(this.key, this.value);
 }
 
-abstract class num implements Comparable<num> {
+sealed class num implements Comparable<num> {
   num operator %(num other);
   num operator *(num other);
   num operator +(num other);
@@ -574,8 +590,13 @@
   external String toString();
   external dynamic noSuchMethod(Invocation invocation);
 
+  @Since("2.14")
   static int hash(Object? object1, Object? object2) => 0;
+
+  @Since("2.14")
   static int hashAll(Iterable<Object?> objects) => 0;
+
+  @Since("2.14")
   static int hashAllUnordered(Iterable<Object?> objects) => 0;
 }
 
@@ -583,7 +604,7 @@
   Iterable<Match> allMatches(String string, [int start = 0]);
 }
 
-abstract class Record {}
+abstract final class Record {}
 
 abstract class RegExp implements Pattern {
   external factory RegExp(String source, {bool unicode = false});
@@ -617,7 +638,7 @@
 
 class StackTrace {}
 
-abstract class String implements Comparable<String>, Pattern {
+abstract final class String implements Comparable<String>, Pattern {
   external factory String.fromCharCodes(Iterable<int> charCodes,
       [int start = 0, int? end]);
 
@@ -684,63 +705,66 @@
   MockSdkLibraryUnit(
     'ffi/ffi.dart',
     '''
+@Since('2.6')
 library dart.ffi;
 
-class NativeType {
+final class NativeType {
   const NativeType();
 }
 
-class Handle extends NativeType {}
+@Since('2.9')
+abstract final class Handle extends NativeType {}
 
-abstract class Opaque extends NativeType {}
+@Since('2.12')
+abstract base class Opaque extends NativeType {}
 
-class Void extends NativeType {}
+final class Void extends NativeType {}
 
-class Int8 extends NativeType {
+final class Int8 extends NativeType {
   const Int8();
 }
 
-class Uint8 extends NativeType {
+final class Uint8 extends NativeType {
   const Uint8();
 }
 
-class Int16 extends NativeType {
+final class Int16 extends NativeType {
   const Int16();
 }
 
-class Uint16 extends NativeType {
+final class Uint16 extends NativeType {
   const Uint16();
 }
 
-class Int32 extends NativeType {
+final class Int32 extends NativeType {
   const Int32();
 }
 
-class Uint32 extends NativeType {
+final class Uint32 extends NativeType {
   const Uint32();
 }
 
-class Int64 extends NativeType {
+final class Int64 extends NativeType {
   const Int64();
 }
 
-class Uint64 extends NativeType {
+final class Uint64 extends NativeType {
   const Uint64();
 }
 
-class Float extends NativeType {
+final class Float extends NativeType {
   const Float();
 }
 
-class Double extends NativeType {
+final class Double extends NativeType {
   const Double();
 }
 
-class IntPtr extends NativeType {
+final class IntPtr extends NativeType {
   const IntPtr();
 }
 
-class Pointer<T extends NativeType> extends NativeType {
+final class Pointer<T extends NativeType> extends NativeType {
   external factory Pointer.fromAddress(int ptr);
 
   static Pointer<NativeFunction<T>> fromFunction<T extends Function>(
@@ -755,19 +779,22 @@
   external DF asFunction<DF extends Function>({bool isLeaf = false});
 }
 
-class _Compound extends NativeType {}
+final class _Compound extends NativeType {}
 
-class Struct extends _Compound {}
+@Since('2.12')
+base class Struct extends _Compound {}
 
-class Union extends _Compound {}
+@Since('2.14')
+base class Union extends _Compound {}
 
-class Packed {
+@Since('2.13')
+final class Packed {
   final int memberAlignment;
 
   const Packed(this.memberAlignment);
 }
 
-abstract class DynamicLibrary {
+abstract final class DynamicLibrary {
   external factory DynamicLibrary.open(String name);
 }
 
@@ -776,13 +803,14 @@
       String symbolName, {bool isLeaf:false});
 }
 
-abstract class NativeFunction<T extends Function> extends NativeType {}
+abstract final class NativeFunction<T extends Function> extends NativeType {}
 
-class DartRepresentationOf {
+final class DartRepresentationOf {
   const DartRepresentationOf(String nativeType);
 }
 
-class Array<T extends NativeType> extends NativeType {
+@Since('2.13')
+final class Array<T extends NativeType> extends NativeType {
   const factory Array(int dimension1,
       [int dimension2,
       int dimension3,
@@ -792,7 +820,7 @@
   const factory Array.multi(List<int> dimensions) = _ArraySize<T>.multi;
 }
 
-class _ArraySize<T extends NativeType> implements Array<T> {
+final class _ArraySize<T extends NativeType> implements Array<T> {
   final int? dimension1;
   final int? dimension2;
   final int? dimension3;
@@ -819,13 +847,14 @@
   external T operator [](int index);
 }
 
-class FfiNative<T> {
+final class FfiNative<T> {
   final String nativeName;
   final bool isLeaf;
   const FfiNative(this.nativeName, {this.isLeaf = false});
 }
 
-class Native<T> {
+@Since('2.19')
+final class Native<T> {
   final String? symbol;
   final String? asset;
   final bool isLeaf;
@@ -837,12 +866,12 @@
   });
 }
 
-class Asset {
+final class Asset {
   final String asset;
   const Asset(this.asset);
 }
 
-class Abi {
+final class Abi {
   static const androidArm = _androidArm;
   static const androidArm64 = _androidArm64;
   static const androidIA32 = _androidIA32;
@@ -878,22 +907,25 @@
   windows,
 }
 
-
-class AbiSpecificInteger extends NativeType {
+@Since('2.16')
+base class AbiSpecificInteger extends NativeType {
   const AbiSpecificInteger();
 }
 
-class AbiSpecificIntegerMapping {
+@Since('2.16')
+final class AbiSpecificIntegerMapping {
   final Map<Abi, NativeType> mapping;
 
   const AbiSpecificIntegerMapping(this.mapping);
 }
 
-abstract class Finalizable {
+@Since('2.17')
+abstract interface class Finalizable {
   factory Finalizable._() => throw UnsupportedError("");
 }
 
-abstract class VarArgs<T extends Record> extends NativeType {}
+@Since('3.0')
+abstract final class VarArgs<T extends Record> extends NativeType {}
 ''',
   )
 ]);
@@ -1365,8 +1397,10 @@
         json.encode({
           'version': 1,
           'experimentSets': {
-            'sdkExperiments': <String>[],
-            'nullSafety': ['non-nullable']
+            'sdkExperiments': <String>[
+              'class-modifiers',
+              'sealed-class',
+            ],
           },
           'sdk': {
             'default': {'experimentSet': 'sdkExperiments'},
diff --git a/analyzer/lib/src/utilities/since_sdk_cache.dart b/analyzer/lib/src/utilities/since_sdk_cache.dart
new file mode 100644
index 0000000..149e914
--- /dev/null
+++ b/analyzer/lib/src/utilities/since_sdk_cache.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2023, 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:pub_semver/pub_semver.dart';
+
+/// The instance of [_SinceSdkCache] that should be used in the analyzer.
+final _SinceSdkCache sinceSdkCache = _SinceSdkCache();
+
+class _SinceSdkCache {
+  final Map<String, Version> _map = {};
+
+  Version? parse(String versionStr) {
+    var result = _map[versionStr];
+    if (result == null) {
+      try {
+        result = Version.parse('$versionStr.0');
+        _map[versionStr] = result;
+      } on FormatException {
+        return null;
+      }
+    }
+    return result;
+  }
+}
diff --git a/analyzer/messages.yaml b/analyzer/messages.yaml
index 69e7bef..5ed1fc2 100644
--- a/analyzer/messages.yaml
+++ b/analyzer/messages.yaml
@@ -308,14 +308,14 @@
 
       #### Example
 
-      Given a file named `a.dart` containing
+      Given a file `a.dart` containing
 
       ```dart
       %uri="lib/a.dart"
       class C {}
       ```
 
-      And a file named `b.dart` containing
+      And a file `b.dart` containing
 
       ```dart
       %uri="lib/b.dart"
@@ -1133,6 +1133,52 @@
     comment: |-
       Parameters:
       0: the name of the base class being implemented
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when an `extends`, `implements`,
+      `with`, or `on` clause uses a class or mixin in a way that isn't allowed
+      given the modifiers on that class or mixin's declaration.
+
+      The message specifies how the declaration is being used and why it isn't
+      allowed.
+
+      #### Example
+
+      Given a file `a.dart` that defines a base class `A`:
+
+      ```dart
+      %uri="lib/a.dart"
+      base class A {}
+      ```
+
+      The following code produces this diagnostic because the class `B`
+      implements the class `A`, but the `base` modifier prevents `A` from being
+      implemented outside of the library where it's defined:
+
+      ```dart
+      import 'a.dart';
+
+      final class B implements [!A!] {}
+      ```
+
+      #### Common fixes
+
+      Use of this type is restricted outside of its declaring library. If a
+      different, unrestricted type is available that can provide similar
+      functionality, then replace the type:
+
+      ```dart
+      class B implements C {}
+      class C {}
+      ```
+
+      If there isn't a different type that would be appropriate, then remove the
+      type, and possibly the whole clause:
+
+      ```dart
+      class B {}
+      ```
   BASE_MIXIN_IMPLEMENTED_OUTSIDE_OF_LIBRARY:
     sharedName: INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
     problemMessage: "The mixin '{0}' can't be implemented outside of its library because it's a base mixin."
@@ -1598,10 +1644,41 @@
       int y = x as int;
       ```
   CLASS_USED_AS_MIXIN:
-    problemMessage: "The class '{0}' can't be used as a mixin because it isn't a mixin class nor a mixin."
+    problemMessage: "The class '{0}' can't be used as a mixin because it's neither a mixin class nor a mixin."
     comment: |-
       Parameters:
       0: the name of the class being used as a mixin
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a class that is neither a
+      `mixin class` nor a `mixin` is used in a `with` clause.
+
+      #### Example
+
+      The following code produces this diagnostic because the class `M` is being
+      used as a mixin, but it isn't defined as a `mixin class`:
+
+      ```dart
+      class M {}
+      class C with [!M!] {}
+      ```
+
+      #### Common fixes
+
+      If the class can be a pure mixin, then change `class` to `mixin`:
+
+      ```dart
+      mixin M {}
+      class C with M {}
+      ```
+
+      If the class needs to be both a class and a mixin, then add `mixin`:
+
+      ```dart
+      mixin class M {}
+      class C with M {}
+      ```
   CLASS_INSTANTIATION_ACCESS_TO_INSTANCE_MEMBER:
     sharedName: CLASS_INSTANTIATION_ACCESS_TO_MEMBER
     problemMessage: "The instance member '{0}' can't be accessed on a class instantiation."
@@ -1680,12 +1757,12 @@
       deferred import. Constants are evaluated at compile time, and values from
       deferred libraries aren't available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
-      Given a file (`a.dart`) that defines the constant `zero`:
+      Given a file `a.dart` that defines the constant `zero`:
 
       ```dart
       %uri="lib/a.dart"
@@ -1731,6 +1808,84 @@
     problemMessage: Constant values from a deferred library can't be used in patterns.
     correctionMessage: Try removing the keyword 'deferred' from the import.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a pattern contains a value
+      declared in a different library, and that library is imported using a
+      deferred import. Constants are evaluated at compile time, but values from
+      deferred libraries aren't available at compile time.
+
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
+
+      #### Example
+
+      Given a file `a.dart` that defines the constant `zero`:
+
+      ```dart
+      %uri="lib/a.dart"
+      const zero = 0;
+      ```
+
+      The following code produces this diagnostic because the constant pattern
+      `a.zero` is imported using a deferred import:
+
+      ```dart
+      import 'a.dart' deferred as a;
+
+      void f(int x) {
+        switch (x) {
+          case a.[!zero!]:
+            // ...
+            break;
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If you need to reference the constant from the imported library, then
+      remove the `deferred` keyword:
+
+      ```dart
+      import 'a.dart' as a;
+
+      void f(int x) {
+        switch (x) {
+          case a.zero:
+            // ...
+            break;
+        }
+      }
+      ```
+
+      If you need to reference the constant from the imported library and also
+      need the imported library to be deferred, then rewrite the switch
+      statement as a sequence of `if` statements:
+
+      ```dart
+      import 'a.dart' deferred as a;
+
+      void f(int x) {
+        if (x == a.zero) {
+          // ...
+        }
+      }
+      ```
+
+      If you don't need to reference the constant, then replace the case
+      expression:
+
+      ```dart
+      void f(int x) {
+        switch (x) {
+          case 0:
+            // ...
+            break;
+        }
+      }
+      ```
   SET_ELEMENT_FROM_DEFERRED_LIBRARY:
     sharedName: COLLECTION_ELEMENT_FROM_DEFERRED_LIBRARY
     problemMessage: "Constant values from a deferred library can't be used as values in a 'const' set literal."
@@ -2376,8 +2531,8 @@
       Constants are evaluated at compile time, and classes from deferred
       libraries aren't available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
@@ -2504,8 +2659,8 @@
       a deferred import. Constants are evaluated at compile time, and values from
       deferred libraries aren't available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
@@ -2587,12 +2742,13 @@
       #### Description
 
       The analyzer produces this diagnostic when the class of object used as a
-      key in a constant map literal implements the `==` operator. The
-      implementation of constant maps uses the `==` operator, so any
-      implementation other than the one inherited from `Object` requires
-      executing arbitrary code at compile time, which isn't supported.
+      key in a constant map literal implements either the `==` operator, the
+      getter `hashCode`, or both. The implementation of constant maps uses both
+      the `==` operator and the `hashCode` getter, so any implementation other
+      than the ones inherited from `Object` requires executing arbitrary code at
+      compile time, which isn't supported.
 
-      #### Example
+      #### Examples
 
       The following code produces this diagnostic because the constant map
       contains a key whose type is `C`, and the class `C` overrides the
@@ -2608,9 +2764,24 @@
       const map = {[!C()!] : 0};
       ```
 
+      The following code produces this diagnostic because the constant map
+      contains a key whose type is `C`, and the class `C` overrides the
+      implementation of `hashCode`:
+
+      ```dart
+      class C {
+        const C();
+
+        int get hashCode => 3;
+      }
+
+      const map = {[!C()!] : 0};
+      ```
+
       #### Common fixes
 
-      If you can remove the implementation of `==` from the class, then do so:
+      If you can remove the implementation of `==` and `hashCode` from the
+      class, then do so:
 
       ```dart
       class C {
@@ -2620,8 +2791,8 @@
       const map = {C() : 0};
       ```
 
-      If you can't remove the implementation of `==` from the class, then make
-      the map be non-constant:
+      If you can't remove the implementation of `==` and `hashCode` from the
+      class, then make the map non-constant:
 
       ```dart
       class C {
@@ -2671,10 +2842,11 @@
       #### Description
 
       The analyzer produces this diagnostic when the class of object used as an
-      element in a constant set literal implements the `==` operator. The
-      implementation of constant sets uses the `==` operator, so any
-      implementation other than the one inherited from `Object` requires
-      executing arbitrary code at compile time, which isn't supported.
+      element in a constant set literal implements either the `==` operator, the
+      getter `hashCode`, or both. The implementation of constant sets uses both
+      the `==` operator and the `hashCode` getter, so any implementation other
+      than the ones inherited from `Object` requires executing arbitrary code at
+      compile time, which isn't supported.
 
       #### Example
 
@@ -2692,9 +2864,24 @@
       const set = {[!C()!]};
       ```
 
+      The following code produces this diagnostic because the constant set
+      contains an element whose type is `C`, and the class `C` overrides the
+      implementation of `hashCode`:
+
+      ```dart
+      class C {
+        const C();
+
+        int get hashCode => 3;
+      }
+
+      const map = {[!C()!]};
+      ```
+
       #### Common fixes
 
-      If you can remove the implementation of `==` from the class, then do so:
+      If you can remove the implementation of `==` and `hashCode` from the
+      class, then do so:
 
       ```dart
       class C {
@@ -2704,8 +2891,8 @@
       const set = {C()};
       ```
 
-      If you can't remove the implementation of `==` from the class, then make
-      the set be non-constant:
+      If you can't remove the implementation of `==` and `hashCode` from the
+      class, then make the set non-constant:
 
       ```dart
       class C {
@@ -2944,6 +3131,48 @@
     problemMessage: The expression of a constant pattern must be a valid constant.
     correctionMessage: Try making the expression a valid constant.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a constant pattern has an
+      expression that isn't a valid constant.
+
+      #### Example
+
+      The following code produces this diagnostic because the constant pattern
+      `i` isn't a constant:
+
+      ```dart
+      void f(int e, int i) {
+        switch (e) {
+          case [!i!]:
+            break;
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If the value that should be matched is known, then replace the expression
+      with a constant:
+
+      ```dart
+      void f(int e, int i) {
+        switch (e) {
+          case 0:
+            break;
+        }
+      }
+      ```
+
+      If the value that should be matched isn't known, then rewrite the code to
+      not use a pattern:
+
+      ```dart
+      void f(int e, int i) {
+        if (e == i) {}
+      }
+      ```
   CONTINUE_LABEL_INVALID:
     previousName: CONTINUE_LABEL_ON_SWITCH
     hasPublishedDocs: true
@@ -3183,12 +3412,12 @@
       library. Extension methods are resolved at compile time, and extensions
       from deferred libraries aren't available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
-      Given a file (`a.dart`) that defines a named extension:
+      Given a file `a.dart` that defines a named extension:
 
       ```dart
       %uri="lib/a.dart"
@@ -3455,6 +3684,37 @@
     comment: |-
       Parameters:
       0: the duplicated name
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when either a record literal or a
+      record type annotation contains a field whose name is the same as a
+      previously declared field in the same literal or type.
+
+      #### Examples
+
+      The following code produces this diagnostic because the record literal has
+      two fields named `a`:
+
+      ```dart
+      var r = (a: 1, [!a!]: 2);
+      ```
+
+      The following code produces this diagnostic because the record type
+      annotation has two fields named `a`, one a positional field and the other
+      a named field:
+
+      ```dart
+      void f((int a, {int [!a!]}) r) {}
+      ```
+
+      #### Common fixes
+
+      Rename one or both of the fields:
+
+      ```dart
+      var r = (a: 1, b: 2);
+      ```
   DUPLICATE_FIELD_FORMAL_PARAMETER:
     problemMessage: "The field '{0}' can't be initialized by multiple parameters in the same constructor."
     correctionMessage: Try removing one of the parameters, or using different fields.
@@ -3565,7 +3825,7 @@
 
       #### Example
 
-      Given a file named `part.dart` containing
+      Given a file `part.dart` containing
 
       ```dart
       %uri="lib/part.dart"
@@ -3597,21 +3857,195 @@
     comment: |-
       Parameters:
       0: the name of the variable
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a single pattern variable is
+      assigned a value more than once in the same pattern assignment.
+
+      #### Example
+
+      The following code produces this diagnostic because the variable `a` is
+      assigned twice in the pattern `(a, a)`:
+
+      ```dart
+      int f((int, int) r) {
+        int a;
+        (a, [!a!]) = r;
+        return a;
+      }
+      ```
+
+      #### Common fixes
+
+      If you need to capture all of the values, then use a unique variable for
+      each of the subpatterns being matched:
+
+      ```dart
+      int f((int, int) r) {
+        int a, b;
+        (a, b) = r;
+        return a + b;
+      }
+      ```
+
+      If some of the values don't need to be captured, then use a wildcard
+      pattern `_` to avoid having to bind the value to a variable:
+
+      ```dart
+      int f((int, int) r) {
+        int a;
+        (_, a) = r;
+        return a;
+      }
+      ```
   DUPLICATE_PATTERN_FIELD:
     problemMessage: The field '{0}' is already matched in this pattern.
     correctionMessage: Try removing the duplicate field.
     comment: |-
       Parameters:
       0: the name of the field
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a record pattern matches the
+      same field more than once, or when an object pattern matches the same
+      getter more than once.
+
+      #### Examples
+
+      The following code produces this diagnostic because the record field `a`
+      is matched twice in the same record pattern:
+
+      ```dart
+      void f(({int a, int b}) r) {
+        switch (r) {
+          case (a: 1, [!a!]: 2):
+            return;
+        }
+      }
+      ```
+
+      The following code produces this diagnostic because the getter `f` is
+      matched twice in the same object pattern:
+
+      ```dart
+      void f(Object o) {
+        switch (0) {
+          case C(f: 1, [!f!]: 2):
+            return;
+        }
+      }
+      class C {
+        int? f;
+      }
+      ```
+
+      #### Common fixes
+
+      If the pattern should match for more than one value of the duplicated
+      field, then use a logical-or pattern:
+
+      ```dart
+      void f(({int a, int b}) r) {
+        switch (r) {
+          case (a: 1, b: _) || (a: 2, b: _):
+            break;
+        }
+      }
+      ```
+
+      If the pattern should match against multiple fields, then change the name
+      of one of the fields:
+
+      ```dart
+      void f(({int a, int b}) r) {
+        switch (r) {
+          case (a: 1, b: 2):
+            return;
+        }
+      }
+      ```
   DUPLICATE_REST_ELEMENT_IN_PATTERN:
     problemMessage: At most one rest element is allowed in a list or map pattern.
     correctionMessage: Try removing the duplicate rest element.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when there's more than one rest
+      pattern in either a list or map pattern. A rest pattern will capture any
+      values unmatched by other subpatterns, making subsequent rest patterns
+      unnecessary because there's nothing left to capture.
+
+      #### Example
+
+      The following code produces this diagnostic because there are two rest
+      patterns in the list pattern:
+
+      ```dart
+      void f(List<int> x) {
+        if (x case [0, ..., [!...!]]) {}
+      }
+      ```
+
+      #### Common fixes
+
+      Remove all but one of the rest patterns:
+
+      ```dart
+      void f(List<int> x) {
+        if (x case [0, ...]) {}
+      }
+      ```
   DUPLICATE_VARIABLE_PATTERN:
     problemMessage: The variable '{0}' is already defined in this pattern.
     correctionMessage: Try renaming the variable.
     comment: |-
       Parameters:
       0: the name of the variable
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a branch of a logical-and
+      pattern declares a variable that is already declared in an earlier branch
+      of the same pattern.
+
+      #### Example
+
+      The following code produces this diagnostic because the variable `a` is
+      declared in both branches of the logical-and pattern:
+
+      ```dart
+      void f((int, int) r) {
+        if (r case (var a, 0) && (0, var [!a!])) {
+          print(a);
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If you need to capture the matched value in multiple branches, then change
+      the names of the variables so that they are unique:
+
+      ```dart
+      void f((int, int) r) {
+        if (r case (var a, 0) && (0, var b)) {
+          print(a + b);
+        }
+      }
+      ```
+
+      If you only need to capture the matched value on one branch, then remove
+      the variable pattern from all but one branch:
+
+      ```dart
+      void f((int, int) r) {
+        if (r case (var a, 0) && (0, _)) {
+          print(a);
+        }
+      }
+      ```
   ENUM_CONSTANT_SAME_NAME_AS_ENCLOSING:
     problemMessage: "The name of the enum constant can't be the same as the enum's name."
     correctionMessage: Try renaming the constant.
@@ -3880,12 +4314,75 @@
     problemMessage: Two keys in a map pattern can't be equal.
     correctionMessage: Change or remove the duplicate key.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a map pattern contains more
+      than one key with the same name. The same key can't be matched twice.
+
+      #### Example
+
+      The following code produces this diagnostic because the key `'a'` appears
+      twice:
+
+      ```dart
+      void f(Map<String, int> x) {
+        if (x case {'a': 1, [!'a'!]: 2}) {}
+      }
+      ```
+
+      #### Common fixes
+
+      If you are trying to match two different keys, then change one of the keys
+      in the pattern:
+
+      ```dart
+      void f(Map<String, int> x) {
+        if (x case {'a': 1, 'b': 2}) {}
+      }
+      ```
+
+      If you are trying to match the same key, but allow any one of multiple
+      patterns to match, the use a logical-or pattern:
+
+      ```dart
+      void f(Map<String, int> x) {
+        if (x case {'a': 1 || 2}) {}
+      }
+      ```
   EXPECTED_ONE_LIST_PATTERN_TYPE_ARGUMENTS:
     problemMessage: List patterns require one type argument or none, but {0} found.
     correctionMessage: Try adjusting the number of type arguments.
     comment: |-
       Parameters:
       0: the number of provided type arguments
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a list pattern has more than
+      one type argument. List patterns can have either zero type arguments or
+      one type argument, but can't have more than one.
+
+      #### Example
+
+      The following code produces this diagnostic because the list pattern
+      (`[0]`) has two type arguments:
+
+      ```dart
+      void f(Object x) {
+        if (x case [!<int, int>!][0]) {}
+      }
+      ```
+
+      #### Common fixes
+
+      Remove all but one of the type arguments:
+
+      ```dart
+      void f(Object x) {
+        if (x case <int>[0]) {}
+      }
+      ```
   EXPECTED_ONE_LIST_TYPE_ARGUMENTS:
     problemMessage: "List literals require one type argument or none, but {0} found."
     correctionMessage: Try adjusting the number of type arguments.
@@ -3979,6 +4476,34 @@
     comment: |-
       Parameters:
       0: the number of provided type arguments
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a map pattern has either one
+      type argument or more than two type arguments. Map patterns can have
+      either two type arguments or zero type arguments, but can't have any other
+      number.
+
+      #### Example
+
+      The following code produces this diagnostic because the map pattern
+      (`<int>{}`) has one type argument:
+
+      ```dart
+      void f(Object x) {
+        if (x case [!<int>!]{}) {}
+      }
+      ```
+
+      #### Common fixes
+
+      Add or remove type arguments until there are two, or none:
+
+      ```dart
+      void f(Object x) {
+        if (x case <int, int>{}) {}
+      }
+      ```
   EXPORT_INTERNAL_LIBRARY:
     problemMessage: "The library '{0}' is internal and can't be exported."
     hasPublishedDocs: true
@@ -4071,7 +4596,7 @@
 
       #### Example
 
-      Given a file named `part.dart` containing
+      Given a file `part.dart` containing
 
       ```dart
       %uri="lib/part.dart"
@@ -5847,7 +6372,7 @@
       in both the `extends` and `with` clauses for the class `B`:
 
       ```dart
-      class A {}
+      mixin class A {}
 
       class B extends A with [!A!] {}
       ```
@@ -6205,12 +6730,79 @@
     comment: |-
       Parameters:
       0: the name of the pattern variable
-  INCONSISTENT_PATTERN_VARIABLE_SHARED_CASE_SCOPE:
-    problemMessage: "The variable '{0}' doesn't have the same type and/or finality in all cases that share this body."
-    correctionMessage: Try declaring the variable pattern with the same type and finality in all cases.
-    comment: |-
-      Parameters:
-      0: the name of the pattern variable
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a pattern variable that is
+      declared on all branches of a logical-or pattern doesn't have the same
+      type on every branch. It is also produced when the variable has a
+      different finality on different branches. A pattern variable declared on
+      multiple branches of a logical-or pattern is required to have the same
+      type and finality in each branch, so that the type and finality of the
+      variable can be known in code that's guarded by the logical-or pattern.
+
+      #### Examples
+
+      The following code produces this diagnostic because the variable `a` is
+      defined to be an `int` on one branch and a `double` on the other:
+
+      ```dart
+      void f(Object? x) {
+        if (x case (int a) || (double [!a!])) {
+          print(a);
+        }
+      }
+      ```
+
+      The following code produces this diagnostic because the variable `a` is
+      `final` in the first branch and isn't `final` in the second branch:
+
+      ```dart
+      void f(Object? x) {
+        if (x case (final int a) || (int [!a!])) {
+          print(a);
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If the finality of the variable is different, decide whether it should be
+      `final` or not `final` and make the cases consistent:
+
+      ```dart
+      void f(Object? x) {
+        if (x case (int a) || (int a)) {
+          print(a);
+        }
+      }
+      ```
+
+      If the type of the variable is different and the type isn't critical to
+      the condition being matched, then ensure that the variable has the same
+      type on both branches:
+
+      ```dart
+      void f(Object? x) {
+        if (x case (num a) || (num a)) {
+          print(a);
+        }
+      }
+      ```
+
+      If the type of the variable is different and the type is critical to the
+      condition being matched, then consider breaking the condition into
+      multiple `if` statements or `case` clauses:
+
+      ```dart
+      void f(Object? x) {
+        if (x case int a) {
+          print(a);
+        } else if (x case double a) {
+          print(a);
+        }
+      }
+      ```
   INITIALIZER_FOR_NON_EXISTENT_FIELD:
     problemMessage: "'{0}' isn't a field in the enclosing class."
     correctionMessage: "Try correcting the name to match an existing field, or defining a field named '{0}'."
@@ -6832,8 +7424,8 @@
       of an annotation. Annotations are evaluated at compile time, and values
       from deferred libraries aren't available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
@@ -6884,8 +7476,8 @@
       are evaluated at compile time, and constants from deferred libraries aren't
       available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
@@ -7168,6 +7760,71 @@
     problemMessage: Record field names can't be the same as a member from 'Object'.
     correctionMessage: Try using a different name for the field.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when either a record literal or a
+      record type annotation has a field whose name is invalid. The name is
+      invalid if it is:
+      - private (starts with `_`)
+      - the same as one of the members defined on `Object`
+      - the same as the name of a positional field (an exception is made if the
+        field is a positional field with the specified name)
+
+      #### Examples
+
+      The following code produces this diagnostic because the record literal has
+      a field named `toString`, which is a method defined on `Object`:
+
+      ```dart
+      var r = (a: 1, [!toString!]: 4);
+      ```
+
+      The following code produces this diagnostic because the record type
+      annotation has a field named `hashCode`, which is a getter defined on
+      `Object`:
+
+      ```dart
+      void f(({int a, int [!hashCode!]}) r) {}
+      ```
+
+      The following code produces this diagnostic because the record literal has
+      a private field named `_a`:
+
+      ```dart
+      var r = ([!_a!]: 1, b: 2);
+      ```
+
+      The following code produces this diagnostic because the record type
+      annotation has a private field named `_a`:
+
+      ```dart
+      void f(({int [!_a!], int b}) r) {}
+      ```
+
+      The following code produces this diagnostic because the record literal has
+      a field named `$1`, which is also the name of a different positional
+      parameter:
+
+      ```dart
+      var r = (2, [!$1!]: 1);
+      ```
+
+      The following code produces this diagnostic because the record type
+      annotation has a field named `$1`, which is also the name of a different
+      positional parameter:
+
+      ```dart
+      void f((int, String, {int [!$1!]}) r) {}
+      ```
+
+      #### Common fixes
+
+      Rename the field:
+
+      ```dart
+      var r = (a: 1, d: 4);
+      ```
   INVALID_FIELD_NAME_PRIVATE:
     sharedName: INVALID_FIELD_NAME
     problemMessage: Record field names can't be private.
@@ -8540,6 +9197,69 @@
   MISSING_OBJECT_PATTERN_GETTER_NAME:
     problemMessage: The getter name is not specified explicitly, and the pattern is not a variable.
     correctionMessage: Try specifying the getter name explicitly, or using a variable pattern.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when, within an object pattern, the
+      specification of a property and the pattern used to match the property's
+      value doesn't have either:
+      - a getter name before the colon
+      - a variable pattern from which the getter name can be inferred
+
+      #### Example
+
+      The following code produces this diagnostic because there is no getter
+      name before the colon and no variable pattern after the colon in the
+      object pattern (`C(:0)`):
+
+      ```dart
+      abstract class C {
+        int get f;
+      }
+
+      void f(C c) {
+        switch (c) {
+          case C([!:0!]):
+            break;
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If you need to use the actual value of the property within the pattern's
+      scope, then add a variable pattern where the name of the variable is the
+      same as the name of the property being matched:
+
+      ```dart
+      abstract class C {
+        int get f;
+      }
+
+      void f(C c) {
+        switch (c) {
+          case C(:var f) when f == 0:
+            print(f);
+        }
+      }
+      ```
+
+      If you don't need to use the actual value of the property within the
+      pattern's scope, then add the name of the property being matched before
+      the colon:
+
+      ```dart
+      abstract class C {
+        int get f;
+      }
+
+      void f(C c) {
+        switch (c) {
+          case C(f: 0):
+            break;
+        }
+      }
+      ```
   MISSING_REQUIRED_ARGUMENT:
     problemMessage: "The named parameter '{0}' is required, but there's no corresponding argument."
     correctionMessage: Try adding the required argument.
@@ -8581,6 +9301,66 @@
     comment: |-
       Parameters:
       0: the name of the variable pattern
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when one branch of a logical-or
+      pattern doesn't declare a variable that is declared on the other branch of
+      the same pattern.
+
+      #### Example
+
+      The following code produces this diagnostic because the right-hand side of
+      the logical-or pattern doesn't declare the variable `a`:
+
+      ```dart
+      void f((int, int) r) {
+        if (r case (var a, 0) || [!(0, _)!]) {
+          print(a);
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If the variable needs to be referenced in the controlled statements, then
+      add a declaration of the variable to every branch of the logical-or
+      pattern:
+
+      ```dart
+      void f((int, int) r) {
+        if (r case (var a, 0) || (0, var a)) {
+          print(a);
+        }
+      }
+      ```
+
+      If the variable doesn't need to be referenced in the controlled
+      statements, then remove the declaration of the variable from every branch
+      of the logical-or pattern:
+
+      ```dart
+      void f((int, int) r) {
+        if (r case (_, 0) || (0, _)) {
+          print('found a zero');
+        }
+      }
+      ```
+
+      If the variable needs to be referenced if one branch of the pattern
+      matches but not when the other matches, then break the pattern into two
+      pieces:
+
+      ```dart
+      void f((int, int) r) {
+        switch (r) {
+          case (var a, 0):
+            print(a);
+          case (0, _):
+            print('found a zero');
+        }
+      }
+      ```
   MIXINS_SUPER_CLASS:
     sharedName: IMPLEMENTS_SUPER_CLASS
     problemMessage: "'{0}' can't be used in both the 'extends' and 'with' clauses."
@@ -8816,6 +9596,11 @@
 
       abstract class B extends A with M {}
       ```
+  MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT:
+    problemMessage: "The class '{0}' can't be declared a mixin because it extends a class other than 'Object'."
+    comment: |-
+      Parameters:
+      0: the name of the mixin class that is invalid
   MIXIN_CLASS_DECLARES_CONSTRUCTOR:
     problemMessage: "The class '{0}' can't be used as a mixin because it declares a constructor."
     hasPublishedDocs: true
@@ -8834,6 +9619,7 @@
       defines a constructor, is being used as a mixin:
 
       ```dart
+      //@dart=2.19
       class A {
         A();
       }
@@ -8856,6 +9642,7 @@
       then do so:
 
       ```dart
+      //@dart=2.19
       class A {
       }
 
@@ -8890,6 +9677,7 @@
       extends `A`, is being used as a mixin by `C`:
 
       ```dart
+      //@dart=2.19
       class A {}
 
       class B extends A {}
@@ -8903,6 +9691,7 @@
       change it:
 
       ```dart
+      //@dart=2.19
       class A {}
 
       class B {}
@@ -9580,12 +10369,12 @@
       constants referenced in case clauses need to be available at compile time,
       and constants from deferred libraries aren't available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
-      Given a file (`a.dart`) that defines the constant `zero`:
+      Given a file `a.dart` that defines the constant `zero`:
 
       ```dart
       %uri="lib/a.dart"
@@ -9709,12 +10498,12 @@
       Default values need to be available at compile time, and constants from
       deferred libraries aren't available at compile time.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
-      Given a file (`a.dart`) that defines the constant `zero`:
+      Given a file `a.dart` that defines the constant `zero`:
 
       ```dart
       %uri="lib/a.dart"
@@ -9877,6 +10666,40 @@
     problemMessage: Key expressions in map patterns must be constants.
     correctionMessage: Try using constants instead.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a key in a map pattern isn't a
+      constant expression.
+
+      #### Example
+
+      The following code produces this diagnostic because the key `A()` isn't a
+      constant:
+
+      ```dart
+      void f(Object x) {
+        if (x case {[!A()!]: 0}) {}
+      }
+
+      class A {
+        const A();
+      }
+      ```
+
+      #### Common fixes
+
+      Use a constant for the key:
+
+      ```dart
+      void f(Object x) {
+        if (x case {const A(): 0}) {}
+      }
+
+      class A {
+        const A();
+      }
+      ```
   NON_CONSTANT_MAP_VALUE:
     problemMessage: The values in a const map literal must be constant.
     correctionMessage: "Try removing the keyword 'const' from the map literal."
@@ -9917,6 +10740,36 @@
     problemMessage: The relational pattern expression must be a constant.
     correctionMessage: Try using a constant instead.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when the value in a relational
+      pattern expression isn't a constant expression.
+
+      #### Example
+
+      The following code produces this diagnostic because the operand of the `>`
+      operator, `a`, isn't a constant:
+
+      ```dart
+      final a = 0;
+
+      void f(int x) {
+        if (x case > [!a!]) {}
+      }
+      ```
+
+      #### Common fixes
+
+      Replace the value with a constant expression:
+
+      ```dart
+      const a = 0;
+
+      void f(int x) {
+        if (x case > a) {}
+      }
+      ```
   NON_CONSTANT_SET_ELEMENT:
     problemMessage: The values in a const set literal must be constants.
     correctionMessage: "Try removing the keyword 'const' from the set literal."
@@ -9963,11 +10816,72 @@
       statement is expected.
   NON_EXHAUSTIVE_SWITCH:
     problemMessage: "The type '{0}' is not exhaustively matched by the switch cases."
-    correctionMessage: "Try adding a default case or cases that match {1}."
+    correctionMessage: "Try adding a default case or cases that match '{1}'."
     comment: |-
       Parameters:
       0: the type of the switch scrutinee
       1: the unmatched space
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a `switch` statement switching
+      over an exhaustive type is missing a case for one or more of the possible
+      values that could flow through it.
+
+      #### Example
+
+      The following code produces this diagnostic because the switch statement
+      doesn't have a case for the value `E.three`, and `E` is an exhaustive
+      type:
+
+      ```dart
+      enum E { one, two, three }
+
+      void f(E e) {
+        [!switch!] (e) {
+          case E.one:
+          case E.two:
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      Add a case for each of the constants that aren't currently being matched:
+
+      ```dart
+      enum E { one, two, three }
+
+      void f(E e) {
+        switch (e) {
+          case E.one:
+          case E.two:
+            break;
+          case E.three:
+        }
+      }
+      ```
+
+      If the missing values don't need to be matched, then add a `default`
+      clause (or a wildcard pattern in a `switch` expression):
+
+      ```dart
+      enum E { one, two, three }
+
+      void f(E e) {
+        switch (e) {
+          case E.one:
+          case E.two:
+            break;
+          default:
+        }
+      }
+      ```
+
+      But be aware that adding a `default` clause or wildcard pattern will cause
+      any future values of the exhaustive type to also be handled, so you will
+      have lost the ability for the compiler to warn you if the `switch` needs
+      to be updated.
   NON_FINAL_FIELD_IN_ENUM:
     problemMessage: Enums can only declare final fields.
     correctionMessage: Try making the field final.
@@ -11225,7 +12139,7 @@
 
       #### Example
 
-      Given a file named `part.dart` containing
+      Given a file `part.dart` containing
 
       ```dart
       %uri="package:a/part.dart"
@@ -11263,7 +12177,7 @@
 
       #### Example
 
-      Given a file (`a.dart`) containing:
+      Given a file `a.dart` containing:
 
       ```dart
       %uri="lib/a.dart"
@@ -11337,8 +12251,61 @@
       part of 'test.dart';
       ```
   PATTERN_ASSIGNMENT_NOT_LOCAL_VARIABLE:
-    problemMessage: Only local variables or formal parameters can be used in pattern assignments.
+    problemMessage: Only local variables can be assigned in pattern assignments.
     correctionMessage: Try assigning to a local variable.
+    comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a pattern assignment assigns a
+      value to anything other than a local variable. Patterns can't assign to
+      fields or top-level variables.
+
+      #### Example
+
+      If the code is cleaner when destructuring with a pattern, then rewrite the
+      code to assign the value to a local variable in a pattern declaration,
+      assigning the non-local variable separately:
+
+      ```dart
+      class C {
+        var x = 0;
+
+        void f((int, int) r) {
+          ([!x!], _) = r;
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If the code is cleaner when using a pattern assignment, then rewrite the
+      code to assign the value to a local variable, assigning the non-local
+      variable separately:
+
+      ```dart
+      class C {
+        var x = 0;
+
+        void f((int, int) r) {
+          var (a, _) = r;
+          x = a;
+        }
+      }
+      ```
+
+      If the code is cleaner without using a pattern assignment, then rewrite
+      the code to not use a pattern assignment:
+
+      ```dart
+      class C {
+        var x = 0;
+
+        void f((int, int) r) {
+          x = r.$1;
+        }
+      }
+      ```
   PATTERN_TYPE_MISMATCH_IN_IRREFUTABLE_CONTEXT:
     problemMessage: "The matched value of type '{0}' isn't assignable to the required type '{1}'."
     correctionMessage: "Try changing the required type of the pattern, or the matched value type."
@@ -11346,9 +12313,248 @@
       Parameters:
       0: the matched type
       1: the required type
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when the type of the value on the
+      right-hand side of a pattern assignment or pattern declaration doesn't
+      match the type required by the pattern being used to match it.
+
+      #### Example
+
+      The following code produces this diagnostic because `x` might not be a
+      `String` and hence might not match the object pattern:
+
+      ```dart
+      void f(Object x) {
+        var [!String(length: a)!] = x;
+        print(a);
+      }
+      ```
+
+      #### Common fixes
+
+      Change the code so that the type of the expression on the right-hand side
+      matches the type required by the pattern:
+
+      ```dart
+      void f(String x) {
+        var String(length: a) = x;
+        print(a);
+      }
+      ```
   PATTERN_VARIABLE_ASSIGNMENT_INSIDE_GUARD:
     problemMessage: Pattern variables can't be assigned inside the guard of the enclosing guarded pattern.
     correctionMessage: Try assigning to a different variable.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a pattern variable is assigned
+      a value inside a guard (`when`) clause.
+
+      #### Example
+
+      The following code produces this diagnostic because the variable `a` is
+      assigned a value inside the guard clause:
+
+      ```dart
+      void f(int x) {
+        if (x case var a when ([!a!] = 1) > 0) {
+          print(a);
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If there's a value you need to capture, then assign it to a different
+      variable:
+
+      ```dart
+      void f(int x) {
+        var b;
+        if (x case var a when (b = 1) > 0) {
+          print(a + b);
+        }
+      }
+      ```
+
+      If there isn't a value you need to capture, then remove the assignment:
+
+      ```dart
+      void f(int x) {
+        if (x case var a when 1 > 0) {
+          print(a);
+        }
+      }
+      ```
+  PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE:
+    sharedName: INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE
+    problemMessage: "The variable '{0}' doesn't have the same type and/or finality in all cases that share this body."
+    correctionMessage: Try declaring the variable pattern with the same type and finality in all cases.
+    comment: |-
+      Parameters:
+      0: the name of the pattern variable
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when multiple case clauses in a
+      switch statement share a body, and at least one of them declares a
+      variable that is referenced in the shared statements, but the variable is
+      either not declared in all of the case clauses or it is declared in
+      inconsistent ways.
+
+      If the variable isn't declared in all of the case clauses, then it won't
+      have a value if one of the clauses that doesn't declare the variable is
+      the one that matches and executes the body. This includes the situation
+      where one of the case clauses is the `default` clause.
+
+      If the variable is declared in inconsistent ways, either being `final` in
+      some cases and not `final` in others or having a different type in
+      different cases, then the semantics of what the type or finality of the
+      variable should be are not defined.
+
+      #### Examples
+
+      The following code produces this diagnostic because the variable `a` is
+      only declared in one of the case clauses, and won't have a value if the
+      second clause is the one that matched `x`:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          case int a when a > 0:
+          case 0:
+            [!a!];
+        }
+      }
+      ```
+
+      The following code produces this diagnostic because the variable `a` isn't
+      declared in the `default` clause, and won't have a value if the body is
+      executed because none of the other clauses matched `x`:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          case int a when a > 0:
+          default:
+            [!a!];
+        }
+      }
+      ```
+
+      The following code produces this diagnostic because the variable `a` won't
+      have a value if the body is executed because a different group of cases
+      caused control to continue at the label:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          someLabel:
+          case int a when a > 0:
+            [!a!];
+          case int b when b < 0:
+            continue someLabel;
+        }
+      }
+      ```
+
+      The following code produces this diagnostic because the variable `a`,
+      while being assigned in all of the case clauses, doesn't have then same
+      type associated with it in every clause:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          case int a when a < 0:
+          case num a when a > 0:
+            [!a!];
+        }
+      }
+      ```
+
+      The following code produces this diagnostic because the variable `a` is
+      `final` in the first case clause and isn't `final` in the second case
+      clause:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          case final int a when a < 0:
+          case int a when a > 0:
+            [!a!];
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If the variable isn't declared in all of the cases, and you need to
+      reference it in the statements, then declare it in the other cases:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          case int a when a > 0:
+          case int a when a == 0:
+            a;
+        }
+      }
+      ```
+
+      If the variable isn't declared in all of the cases, and you don't need to
+      reference it in the statements, then remove the references to it and
+      remove the declarations from the other cases:
+
+      ```dart
+      void f(int x) {
+        switch (x) {
+          case > 0:
+          case 0:
+        }
+      }
+      ```
+
+      If the type of the variable is different, decide the type the variable
+      should have and make the cases consistent:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          case num a when a < 0:
+          case num a when a > 0:
+            a;
+        }
+      }
+      ```
+
+      If the finality of the variable is different, decide whether it should be
+      `final` or not `final` and make the cases consistent:
+
+      ```dart
+      void f(Object? x) {
+        switch (x) {
+          case final int a when a < 0:
+          case final int a when a > 0:
+            a;
+        }
+      }
+      ```
+  PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL:
+    sharedName: INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE
+    problemMessage: "The variable '{0}' is not available because there is a label or 'default' case."
+    correctionMessage: Try removing the label, or providing the 'default' case with its own body.
+    comment: |-
+      Parameters:
+      0: the name of the pattern variable
+  PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES:
+    sharedName: INVALID_PATTERN_VARIABLE_IN_SHARED_CASE_SCOPE
+    problemMessage: "The variable '{0}' is available in some, but not all cases that share this body."
+    correctionMessage: Try declaring the variable pattern with the same type and finality in all cases.
+    comment: |-
+      Parameters:
+      0: the name of the pattern variable
   POSITIONAL_SUPER_FORMAL_PARAMETER_WITH_POSITIONAL_ARGUMENT:
     problemMessage: Positional super parameters can't be used when the super constructor invocation has a positional argument.
     correctionMessage: Try making all the positional parameters passed to the super constructor be either all super parameters or all normal parameters.
@@ -11568,7 +12774,7 @@
 
       #### Example
 
-      Given a file named `a.dart` containing the following code:
+      Given a file `a.dart` containing the following code:
 
       ```dart
       %uri="lib/a.dart"
@@ -11648,7 +12854,7 @@
 
       #### Example
 
-      Given a file named `a.dart` that contains the following:
+      Given a file `a.dart` that contains the following:
 
       ```dart
       %uri="lib/a.dart"
@@ -12448,15 +13654,183 @@
   REFUTABLE_PATTERN_IN_IRREFUTABLE_CONTEXT:
     problemMessage: Refutable patterns can't be used in an irrefutable context.
     correctionMessage: Try using an if-case, a 'switch' statement, or a 'switch' expression instead.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a [refutable pattern][] is used
+      in a context where only an [irrefutable pattern][] is allowed.
+
+      The refutable patterns that are disallowed are:
+      - logical-or
+      - relational
+      - null-check
+      - constant
+
+      The contexts that are checked are:
+      - pattern-based variable declarations
+      - pattern-based for loops
+      - assignments with a pattern on the left-hand side
+
+      #### Example
+
+      The following code produces this diagnostic because the null-check
+      pattern, which is a refutable pattern, is in a pattern-based variable
+      declaration, which doesn't allow refutable patterns:
+
+      ```dart
+      void f(int? x) {
+        var ([!_?!]) = x;
+      }
+      ```
+
+      #### Common fixes
+
+      Rewrite the code to not use a refutable pattern in an irrefutable context.
+  RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE:
+    problemMessage: "The constant expression type '{0}' is not assignable to the parameter type '{1}' of the '{2}' operator."
+    comment: |-
+      Parameters:
+      0: the operand type
+      1: the parameter type of the invoked operator
+      2: the name of the invoked operator
   RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL:
     problemMessage: The return type of operators used in relational patterns must be assignable to 'bool'.
     correctionMessage: Try updating the operator declaration to return 'bool'.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a relational pattern references
+      an operator that doesn't produce a value of type `bool`.
+
+      #### Example
+
+      The following code produces this diagnostic because the operator `>`, used
+      in the relational pattern `> c2`, returns a value of type `int` rather
+      than a `bool`:
+
+      ```dart
+      class C {
+        const C();
+
+        int operator >(C c) => 3;
+
+        bool operator <(C c) => false;
+      }
+
+      const C c2 = C();
+
+      void f(C c1) {
+        if (c1 case [!>!] c2) {}
+      }
+      ```
+
+      #### Common fixes
+
+      If there's a different operator that should be used, then change the
+      operator:
+
+      ```dart
+      class C {
+        const C();
+
+        int operator >(C c) => 3;
+
+        bool operator <(C c) => false;
+      }
+
+      const C c2 = C();
+
+      void f(C c1) {
+        if (c1 case < c2) {}
+      }
+      ```
+
+      If the operator is expected to return `bool`, then update the declaration
+      of the operator:
+
+      ```dart
+      class C {
+        const C();
+
+        bool operator >(C c) => true;
+
+        bool operator <(C c) => false;
+      }
+
+      const C c2 = C();
+
+      void f(C c1) {
+        if (c1 case > c2) {}
+      }
+      ```
   REST_ELEMENT_WITH_SUBPATTERN_IN_MAP_PATTERN:
     problemMessage: A rest element in a map pattern can't have a subpattern.
     correctionMessage: Try removing the subpattern.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a rest pattern in a map pattern
+      has a subpattern. In Dart, there is no notion of a subset of a map, so
+      there isn't anything to match against the subpattern.
+
+      #### Example
+
+      The following code produces this diagnostic because the rest pattern has a
+      subpattern:
+
+      ```dart
+      void f(Map<String, int> m) {
+        switch (m) {
+          case {'a': var a, ... [!> 0!]}:
+            print(a);
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      Remove the subpattern:
+
+      ```dart
+      void f(Map<String, int> m) {
+        switch (m) {
+          case {'a': var a, ...}:
+            print(a);
+        }
+      }
+      ```
   REST_ELEMENT_NOT_LAST_IN_MAP_PATTERN:
     problemMessage: A rest element in a map pattern must be the last element.
     correctionMessage: Try moving the rest element to be the last element.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a map pattern contains entries
+      after a rest pattern. The rest pattern will match map entries whose keys
+      aren't matched by any of the entries in the pattern. To make those
+      semantics clear, the language requires that the rest pattern be the last
+      entry in the list.
+
+      #### Example
+
+      The following code produces this diagnostic because the rest pattern is
+      followed by another map pattern entry (`0: _`):
+
+      ```dart
+      void f(Map<int, String> x) {
+        if (x case {[!...!], 0: _}) {}
+      }
+      ```
+
+      #### Common fixes
+
+      Move the rest pattern to the end of the map pattern:
+
+      ```dart
+      void f(Map<int, String> x) {
+        if (x case {0: _, ...}) {}
+      }
+      ```
   RETHROW_OUTSIDE_CATCH:
     problemMessage: A rethrow must be inside of a catch clause.
     correctionMessage: "Try moving the expression into a catch clause, or using a 'throw' expression."
@@ -12886,6 +14260,46 @@
 
       int f(C c) => c.b;
       ```
+  SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED:
+    sharedName: SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
+    problemMessage: "The type '{0}' must be 'base', 'final' or 'sealed' because the supertype '{1}' is 'base'."
+    comment: |-
+      Parameters:
+      0: the name of the subtype that is not 'base', 'final', or 'sealed'
+      1: the name of the 'base' supertype
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a class or mixin has a direct
+      or indirect supertype that is either `base` or `final`, but the class or
+      mixin itself isn't marked either `base`, `final`, or `sealed`.
+
+      #### Example
+
+      The following code produces this diagnostic because the class `B` is a
+      subtype of `A`, and `A` is a `base` class, but `B` is neither `base`,
+      `final` or `sealed`:
+
+      ```dart
+      base class A {}
+      class [!B!] extends A {}
+      ```
+
+      #### Common fixes
+
+      Add either `base`, `final` or `sealed` to the class or mixin declaration:
+
+      ```dart
+      base class A {}
+      final class B extends A {}
+      ```
+  SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED:
+    sharedName: SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
+    problemMessage: "The type '{0}' must be 'base', 'final' or 'sealed' because the supertype '{1}' is 'final'."
+    comment: |-
+      Parameters:
+      0: the name of the subtype that is not 'base', 'final', or 'sealed'
+      1: the name of the 'final' supertype
   SUPER_FORMAL_PARAMETER_TYPE_IS_NOT_SUBTYPE_OF_ASSOCIATED:
     problemMessage: The type '{0}' of this parameter isn't a subtype of the type '{1}' of the associated super constructor parameter.
     correctionMessage: Try removing the explicit type annotation from the parameter.
@@ -13132,12 +14546,12 @@
       classes from deferred libraries aren't compiled until the library is
       loaded.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
-      Given a file (`a.dart`) that defines the class `A`:
+      Given a file `a.dart` that defines the class `A`:
 
       ```dart
       %uri="lib/a.dart"
@@ -13624,7 +15038,7 @@
 
       #### Common fixes
 
-      Add an explicit null check to the expression:
+      Add an explicit null-check to the expression:
 
       ```dart
       void f(String? s) {
@@ -13724,8 +15138,8 @@
       is a type declared in a library that is imported using a deferred import.
       These types are required to be available at compile time, but aren't.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
@@ -15504,6 +16918,35 @@
   VARIABLE_PATTERN_KEYWORD_IN_DECLARATION_CONTEXT:
     problemMessage: Variable patterns in declaration context can't specify 'var' or 'final' keyword.
     correctionMessage: Try removing the keyword.
+    comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a variable pattern is used
+      within a declaration context.
+
+      #### Example
+
+      The following code produces this diagnostic because the variable patterns
+      in the record pattern are in a declaration context:
+
+      ```dart
+      void f((int, int) r) {
+        var ([!var!] x, y) = r;
+        print(x + y);
+      }
+      ```
+
+      #### Common fixes
+
+      Remove the `var` or `final` keyword(s) within the variable pattern:
+
+      ```dart
+      void f((int, int) r) {
+        var (x, y) = r;
+        print(x + y);
+      }
+      ```
   VARIABLE_TYPE_MISMATCH:
     problemMessage: "A value of type '{0}' can't be assigned to a const variable of type '{1}'."
     correctionMessage: "Try using a subtype, or removing the 'const' keyword"
@@ -16087,7 +17530,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class [!C!] extends AbiSpecificInteger {
+      final class [!C!] extends AbiSpecificInteger {
       }
       ```
 
@@ -16098,7 +17541,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class [!C!] extends AbiSpecificInteger {
+      final class [!C!] extends AbiSpecificInteger {
         C();
       }
       ```
@@ -16110,7 +17553,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class [!C!] extends AbiSpecificInteger {
+      final class [!C!] extends AbiSpecificInteger {
         const C.zero();
         const C.one();
       }
@@ -16123,7 +17566,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class [!C!] extends AbiSpecificInteger {
+      final class [!C!] extends AbiSpecificInteger {
         final int i;
 
         const C(this.i);
@@ -16137,7 +17580,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class [!C!]<T> extends AbiSpecificInteger { // type parameters
+      final class [!C!]<T> extends AbiSpecificInteger { // type parameters
         const C();
       }
       ```
@@ -16151,7 +17594,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class C extends AbiSpecificInteger {
+      final class C extends AbiSpecificInteger {
         const C();
       }
       ```
@@ -16177,7 +17620,7 @@
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
       @[!AbiSpecificIntegerMapping!]({Abi.linuxX64 : Uint16()})
-      class C extends AbiSpecificInteger {
+      final class C extends AbiSpecificInteger {
         const C();
       }
       ```
@@ -16191,7 +17634,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8(), Abi.linuxX64 : Uint16()})
-      class C extends AbiSpecificInteger {
+      final class C extends AbiSpecificInteger {
         const C();
       }
       ```
@@ -16215,7 +17658,7 @@
       ```dart
       import 'dart:ffi';
 
-      class [!C!] extends AbiSpecificInteger {
+      final class [!C!] extends AbiSpecificInteger {
         const C();
       }
       ```
@@ -16228,7 +17671,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class C extends AbiSpecificInteger {
+      final class C extends AbiSpecificInteger {
         const C();
       }
       ```
@@ -16263,7 +17706,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : [!Array<Uint8>(4)!]})
-      class C extends AbiSpecificInteger {
+      final class C extends AbiSpecificInteger {
         const C();
       }
       ```
@@ -16276,7 +17719,7 @@
       import 'dart:ffi';
 
       @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-      class C extends AbiSpecificInteger {
+      final class C extends AbiSpecificInteger {
         const C();
       }
       ```
@@ -16303,7 +17746,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         [!@Double()!]
         external Pointer<Int8> p;
       }
@@ -16316,7 +17759,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer<Int8> p;
       }
       ```
@@ -16401,7 +17844,7 @@
       ```dart
       import 'dart:ffi';
 
-      class [!S!] extends Struct implements Finalizable {
+      final class [!S!] extends Struct implements Finalizable {
         external Pointer notEmpty;
       }
       ```
@@ -16413,7 +17856,7 @@
       ```dart
       import 'dart:ffi';
 
-      class S extends Struct {
+      final class S extends Struct {
         external Pointer notEmpty;
       }
       ```
@@ -16438,7 +17881,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         external int a;
       }
@@ -16457,7 +17900,7 @@
       import 'dart:ffi';
       import 'package:ffi/ffi.dart';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         external int a;
       }
@@ -16494,7 +17937,7 @@
       ```dart
       import 'dart:ffi';
 
-      class [!C!] extends Struct {}
+      final class [!C!] extends Struct {}
       ```
 
       #### Common fixes
@@ -16504,7 +17947,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         external int x;
       }
@@ -16516,7 +17959,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Opaque {}
+      final class C extends Opaque {}
       ```
 
       If the class isn't intended to be a struct, then remove or change the
@@ -16547,7 +17990,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         [!@Int16()!]
         external int x;
@@ -16560,7 +18003,7 @@
 
       ```dart
       import 'dart:ffi';
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         external int x;
       }
@@ -16587,7 +18030,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Array(4)
         [!@Array(8)!]
         external Array<Uint8> a0;
@@ -16601,7 +18044,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Array(8)
         external Array<Uint8> a0;
       }
@@ -16650,7 +18093,7 @@
       // @dart = 2.9
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         int f;
 
@@ -16666,7 +18109,7 @@
       // @dart = 2.9
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         int f;
 
@@ -16695,7 +18138,7 @@
       // @dart = 2.9
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         Pointer [!p!] = nullptr;
       }
       ```
@@ -16708,7 +18151,7 @@
       // @dart = 2.9
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         Pointer p;
       }
       ```
@@ -16733,7 +18176,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int16()
         int [!a!];
       }
@@ -16746,7 +18189,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int16()
         external int a;
       }
@@ -16774,7 +18217,7 @@
       ```dart
       import 'dart:ffi';
 
-      class [!S!]<T> extends Struct {
+      final class [!S!]<T> extends Struct {
         external Pointer notEmpty;
       }
       ```
@@ -16786,7 +18229,7 @@
       ```dart
       import 'dart:ffi';
 
-      class S extends Struct {
+      final class S extends Struct {
         external Pointer notEmpty;
       }
       ```
@@ -16862,7 +18305,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         external [!String!] s;
 
         @Int32()
@@ -16878,7 +18321,7 @@
       import 'dart:ffi';
       import 'package:ffi/ffi.dart';
 
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer<Utf8> s;
 
         @Int32()
@@ -17014,7 +18457,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         [!@Double()!]
         external int x;
       }
@@ -17027,7 +18470,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         external int x;
       }
@@ -17038,7 +18481,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Double()
         external double x;
       }
@@ -17070,7 +18513,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         external [!int!] x;
       }
       ```
@@ -17082,7 +18525,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int64()
         external int x;
       }
@@ -17154,7 +18597,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         external var [!str!];
 
         @Int32()
@@ -17170,7 +18613,7 @@
       import 'dart:ffi';
       import 'package:ffi/ffi.dart';
 
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer<Utf8> str;
 
         @Int32()
@@ -17199,7 +18642,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         external [!Array<Uint8>!] a0;
       }
       ```
@@ -17211,7 +18654,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Array(8)
         external Array<Uint8> a0;
       }
@@ -17454,7 +18897,7 @@
       ```dart
       import 'dart:ffi';
 
-      class MyStruct extends Struct {
+      final class MyStruct extends Struct {
         @Array([!-8!])
         external Array<Uint8> a0;
       }
@@ -17467,7 +18910,7 @@
       ```dart
       import 'dart:ffi';
 
-      class MyStruct extends Struct {
+      final class MyStruct extends Struct {
         @Array(8)
         external Array<Uint8> a0;
       }
@@ -17498,7 +18941,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Array(8)
         external Array<[!Void!]> a0;
       }
@@ -17511,7 +18954,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Array(8)
         external Array<Uint8> a0;
       }
@@ -17539,7 +18982,7 @@
 
       @Packed(1)
       [!@Packed(1)!]
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer<Uint8> notEmpty;
       }
       ```
@@ -17552,7 +18995,7 @@
       import 'dart:ffi';
 
       @Packed(1)
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer<Uint8> notEmpty;
       }
       ```
@@ -17578,7 +19021,7 @@
       import 'dart:ffi';
 
       @Packed([!3!])
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer<Uint8> notEmpty;
       }
       ```
@@ -17591,7 +19034,7 @@
       import 'dart:ffi';
 
       @Packed(4)
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer<Uint8> notEmpty;
       }
       ```
@@ -17618,7 +19061,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         [!@Array(8, 8)!]
         external Array<Array<Array<Uint8>>> a0;
       }
@@ -17632,7 +19075,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Array(8, 8, 4)
         external Array<Array<Array<Uint8>>> a0;
       }
@@ -17643,7 +19086,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Array(8, 8)
         external Array<Array<Uint8>> a0;
       }
@@ -17675,7 +19118,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends [!Double!] {}
+      final class C extends [!Double!] {}
       ```
 
       #### Common fixes
@@ -17686,7 +19129,7 @@
       ```dart
       import 'dart:ffi';
 
-      class C extends Struct {
+      final class C extends Struct {
         @Int32()
         external int i;
       }
@@ -17696,7 +19139,7 @@
       references to FFI classes:
 
       ```dart
-      class C {}
+      final class C {}
       ```
   SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS:
     sharedName: SUBTYPE_OF_FFI_CLASS
@@ -17742,11 +19185,11 @@
       ```dart
       import 'dart:ffi';
 
-      class S extends Struct {
+      final class S extends Struct {
         external Pointer f;
       }
 
-      class C extends [!S!] {
+      final class C extends [!S!] {
         external Pointer g;
       }
       ```
@@ -17760,11 +19203,11 @@
       ```dart
       import 'dart:ffi';
 
-      class S extends Struct {
+      final class S extends Struct {
         external Pointer f;
       }
 
-      class C extends Struct {
+      final class C extends Struct {
         external Pointer f;
 
         external Pointer g;
@@ -17789,41 +19232,6 @@
       1: the name of the class being extended, implemented, or mixed in
     hasPublishedDocs: true
 HintCode:
-  ASSIGNMENT_OF_DO_NOT_STORE:
-    problemMessage: "'{0}' is marked 'doNotStore' and shouldn't be assigned to a field or top-level variable."
-    correctionMessage: Try removing the assignment.
-    comment: |-
-      Users should not assign values marked `@doNotStore`.
-
-      Parameters:
-      0: the name of the field or variable
-    hasPublishedDocs: true
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when the value of a function
-      (including methods and getters) that is explicitly or implicitly marked by
-      the `[doNotStore][meta-doNotStore]` annotation is stored in either a field
-      or top-level variable.
-
-      #### Example
-
-      The following code produces this diagnostic because the value of the
-      function `f` is being stored in the top-level variable `x`:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      @doNotStore
-      int f() => 1;
-
-      var x = [!f()!];
-      ```
-
-      #### Common fixes
-
-      Replace references to the field or variable with invocations of the
-      function producing the value.
   CAN_BE_NULL_AFTER_NULL_AWARE:
     problemMessage: "The receiver uses '?.', so its value can be null."
     correctionMessage: "Replace the '.' with a '?.' in the invocation."
@@ -17832,180 +19240,18 @@
       subsequent invocations should also use '?.' operator.
 
       Note: This diagnostic is only generated in pre-null safe code.
-  DEAD_CODE:
-    problemMessage: Dead code.
-    correctionMessage: Try removing the code, or fixing the code before it so that it can be reached.
-    hasPublishedDocs: true
-    comment: |-
-      Dead code is code that is never reached, this can happen for instance if a
-      statement follows a return statement.
 
-      No parameters.
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when code is found that won't be
-      executed because execution will never reach the code.
-
-      #### Example
-
-      The following code produces this diagnostic because the invocation of
-      `print` occurs after the function has returned:
-
-      ```dart
-      void f() {
-        return;
-        [!print('here');!]
-      }
-      ```
-
-      #### Common fixes
-
-      If the code isn't needed, then remove it:
-
-      ```dart
-      void f() {
-        return;
-      }
-      ```
-
-      If the code needs to be executed, then either move the code to a place
-      where it will be executed:
-
-      ```dart
-      void f() {
-        print('here');
-        return;
-      }
-      ```
-
-      Or, rewrite the code before it, so that it can be reached:
-
-      ```dart
-      void f({bool skipPrinting = true}) {
-        if (skipPrinting) {
-          return;
-        }
-        print('here');
-      }
-      ```
-  DEAD_CODE_CATCH_FOLLOWING_CATCH:
-    problemMessage: "Dead code: Catch clauses after a 'catch (e)' or an 'on Object catch (e)' are never reached."
-    correctionMessage: Try reordering the catch clauses so that they can be reached, or removing the unreachable catch clauses.
-    hasPublishedDocs: true
-    comment: |-
-      Dead code is code that is never reached. This case covers cases where the
-      user has catch clauses after `catch (e)` or `on Object catch (e)`.
-
-      No parameters.
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a `catch` clause is found that
-      can't be executed because it's after a `catch` clause of the form
-      `catch (e)` or `on Object catch (e)`. The first `catch` clause that matches
-      the thrown object is selected, and both of those forms will match any
-      object, so no `catch` clauses that follow them will be selected.
-
-      #### Example
-
-      The following code produces this diagnostic:
-
-      ```dart
-      void f() {
-        try {
-        } catch (e) {
-        } [!on String {
-        }!]
-      }
-      ```
-
-      #### Common fixes
-
-      If the clause should be selectable, then move the clause before the general
-      clause:
-
-      ```dart
-      void f() {
-        try {
-        } on String {
-        } catch (e) {
-        }
-      }
-      ```
-
-      If the clause doesn't need to be selectable, then remove it:
-
-      ```dart
-      void f() {
-        try {
-        } catch (e) {
-        }
-      }
-      ```
-  DEAD_CODE_ON_CATCH_SUBTYPE:
-    problemMessage: "Dead code: This on-catch block won't be executed because '{0}' is a subtype of '{1}' and hence will have been caught already."
-    correctionMessage: Try reordering the catch clauses so that this block can be reached, or removing the unreachable catch clause.
-    hasPublishedDocs: true
-    comment: |-
-      Dead code is code that is never reached. This case covers cases where the
-      user has an on-catch clause such as `on A catch (e)`, where a supertype of
-      `A` was already caught.
-
-      Parameters:
-      0: name of the subtype
-      1: name of the supertype
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a `catch` clause is found that
-      can't be executed because it is after a `catch` clause that catches either
-      the same type or a supertype of the clause's type. The first `catch` clause
-      that matches the thrown object is selected, and the earlier clause always
-      matches anything matchable by the highlighted clause, so the highlighted
-      clause will never be selected.
-
-      #### Example
-
-      The following code produces this diagnostic:
-
-      ```dart
-      void f() {
-        try {
-        } on num {
-        } [!on int {
-        }!]
-      }
-      ```
-
-      #### Common fixes
-
-      If the clause should be selectable, then move the clause before the general
-      clause:
-
-      ```dart
-      void f() {
-        try {
-        } on int {
-        } on num {
-        }
-      }
-      ```
-
-      If the clause doesn't need to be selectable, then remove it:
-
-      ```dart
-      void f() {
-        try {
-        } on num {
-        }
-      }
-      ```
+      Note: Since this diagnostic is only produced in pre-null safe code, we do
+      not plan to go through the exercise of converting it to a Warning.
   DEPRECATED_COLON_FOR_DEFAULT_VALUE:
     problemMessage: Using a colon as a separator before a default value is deprecated and will not be supported in language version 3.0 and later.
     correctionMessage: Try replacing the colon with an equal sign.
     hasPublishedDocs: true
-    comment: No parameters.
+    comment: |-
+      No parameters.
+
+      Note: Since this diagnostic is only produced in pre-3.0 code, we do not
+      plan to go through the exercise of converting it to a Warning.
     documentation: |-
       #### Description
 
@@ -18189,24 +19435,6 @@
       ```dart
       int divide(num x, num y) => x ~/ y;
       ```
-  FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE:
-    problemMessage: "A file in the 'lib' directory shouldn't import a file outside the 'lib' directory."
-    correctionMessage: "Try removing the import, or moving the imported file inside the 'lib' directory."
-    comment: |-
-      It is a bad practice for a source file in a package "lib" directory
-      hierarchy to traverse outside that directory hierarchy. For example, a
-      source file in the "lib" directory should not contain a directive such as
-      `import '../web/some.dart'` which references a file outside the lib
-      directory.
-  FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE:
-    problemMessage: "A file outside the 'lib' directory shouldn't reference a file inside the 'lib' directory using a relative path."
-    correctionMessage: "Try using a 'package:' URI instead."
-    comment: |-
-      It is a bad practice for a source file outside a package "lib" directory
-      hierarchy to traverse into that directory hierarchy. For example, a source
-      file in the "web" directory should not contain a directive such as
-      `import '../lib/some.dart'` which references a file inside the lib
-      directory.
   IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION:
     problemMessage: "The imported library defines a top-level function named 'loadLibrary' that is hidden by deferring this library."
     correctionMessage: Try changing the import to not be deferred, or rename the function in the imported library.
@@ -18221,12 +19449,12 @@
       function is used to load the contents of the deferred library, and the
       implicit function hides the explicit declaration in the deferred library.
 
-      For more information, see the language tour's coverage of
-      [deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+      For more information, check out
+      [Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
       #### Example
 
-      Given a file (`a.dart`) that defines a function named `loadLibrary`:
+      Given a file `a.dart` that defines a function named `loadLibrary`:
 
       ```dart
       %uri="lib/a.dart"
@@ -18297,7 +19525,7 @@
 
       #### Example
 
-      Given a file named `a.dart` that contains the following:
+      Given a file `a.dart` that contains the following:
 
       ```dart
       %uri="lib/a.dart"
@@ -18323,322 +19551,6 @@
       If you can't migrate the imported library, then the importing library
       needs to have a language version that is before 2.12, when null safety was
       enabled by default.
-  MIXIN_ON_SEALED_CLASS:
-    problemMessage: "The class '{0}' shouldn't be used as a mixin constraint because it is sealed, and any class mixing in this mixin must have '{0}' as a superclass."
-    correctionMessage: Try composing with this class, or refer to its documentation for more information.
-    hasPublishedDocs: true
-    comment: |-
-      This hint is generated anywhere where a `@sealed` class is used as a
-      a superclass constraint of a mixin.
-
-      Parameters:
-      0: the name of the sealed class
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when the superclass constraint of a
-      mixin is a class from a different package that was marked as
-      `[sealed][meta-sealed]`. Classes that are sealed can't be extended,
-      implemented, mixed in, or used as a superclass constraint.
-
-      #### Example
-
-      If the package `p` defines a sealed class:
-
-      ```dart
-      %uri="package:p/p.dart"
-      import 'package:meta/meta.dart';
-
-      @sealed
-      class C {}
-      ```
-
-      Then, the following code, when in a package other than `p`, produces this
-      diagnostic:
-
-      ```dart
-      import 'package:p/p.dart';
-
-      [!mixin M on C {}!]
-      ```
-
-      #### Common fixes
-
-      If the classes that use the mixin don't need to be subclasses of the sealed
-      class, then consider adding a field and delegating to the wrapped instance
-      of the sealed class.
-  NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW:
-    sharedName: NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR
-    problemMessage: "This instance creation must be 'const', because the {0} constructor is marked as '@literal'."
-    correctionMessage: "Try replacing the 'new' keyword with 'const'."
-    hasPublishedDocs: true
-    comment: |-
-      Generate a hint for non-const instance creation (with the `new` keyword)
-      using a constructor annotated with `@literal`.
-
-      Parameters:
-      0: the name of the class defining the annotated constructor
-  NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR:
-    problemMessage: "This instance creation must be 'const', because the {0} constructor is marked as '@literal'."
-    correctionMessage: "Try adding a 'const' keyword."
-    hasPublishedDocs: true
-    comment: |-
-      Generate a hint for non-const instance creation using a constructor
-      annotated with `@literal`.
-
-      Parameters:
-      0: the name of the class defining the annotated constructor
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a constructor that has the
-      `[literal][meta-literal]` annotation is invoked without using the `const`
-      keyword, but all of the arguments to the constructor are constants. The
-      annotation indicates that the constructor should be used to create a
-      constant value whenever possible.
-
-      #### Example
-
-      The following code produces this diagnostic:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      class C {
-        @literal
-        const C();
-      }
-
-      C f() => [!C()!];
-      ```
-
-      #### Common fixes
-
-      Add the keyword `const` before the constructor invocation:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      class C {
-        @literal
-        const C();
-      }
-
-      void f() => const C();
-      ```
-  NULL_CHECK_ALWAYS_FAILS:
-    problemMessage: "This null-check will always throw an exception because the expression will always evaluate to 'null'."
-    comment: |-
-      No parameters.
-    hasPublishedDocs: true
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when the null check operator (`!`)
-      is used on an expression whose value can only be `null`. In such a case
-      the operator always throws an exception, which likely isn't the intended
-      behavior.
-
-      #### Example
-
-      The following code produces this diagnostic because the function `g` will
-      always return `null`, which means that the null check in `f` will always
-      throw:
-
-      ```dart
-      void f() {
-        [!g()!!];
-      }
-
-      Null g() => null;
-      ```
-
-      #### Common fixes
-
-      If you intend to always throw an exception, then replace the null check
-      with an explicit `throw` expression to make the intent more clear:
-
-      ```dart
-      void f() {
-        g();
-        throw TypeError();
-      }
-
-      Null g() => null;
-      ```
-  OVERRIDE_ON_NON_OVERRIDING_FIELD:
-    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
-    problemMessage: "The field doesn't override an inherited getter or setter."
-    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
-    hasPublishedDocs: true
-    comment: |-
-      A field with the override annotation does not override a getter or setter.
-
-      No parameters.
-  OVERRIDE_ON_NON_OVERRIDING_GETTER:
-    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
-    problemMessage: "The getter doesn't override an inherited getter."
-    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
-    hasPublishedDocs: true
-    comment: |-
-      A getter with the override annotation does not override an existing getter.
-
-      No parameters.
-  OVERRIDE_ON_NON_OVERRIDING_METHOD:
-    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
-    problemMessage: "The method doesn't override an inherited method."
-    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
-    hasPublishedDocs: true
-    comment: |-
-      A method with the override annotation does not override an existing method.
-
-      No parameters.
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a class member is annotated with
-      the `@override` annotation, but the member isn't declared in any of the
-      supertypes of the class.
-
-      #### Example
-
-      The following code produces this diagnostic because `m` isn't declared in
-      any of the supertypes of `C`:
-
-      ```dart
-      class C {
-        @override
-        String [!m!]() => '';
-      }
-      ```
-
-      #### Common fixes
-
-      If the member is intended to override a member with a different name, then
-      update the member to have the same name:
-
-      ```dart
-      class C {
-        @override
-        String toString() => '';
-      }
-      ```
-
-      If the member is intended to override a member that was removed from the
-      superclass, then consider removing the member from the subclass.
-
-      If the member can't be removed, then remove the annotation.
-  OVERRIDE_ON_NON_OVERRIDING_SETTER:
-    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
-    problemMessage: "The setter doesn't override an inherited setter."
-    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
-    hasPublishedDocs: true
-    comment: |-
-      A setter with the override annotation does not override an existing setter.
-
-      No parameters.
-  STRICT_RAW_TYPE:
-    problemMessage: "The generic type '{0}' should have explicit type arguments but doesn't."
-    correctionMessage: "Use explicit type arguments for '{0}'."
-    comment: |-
-      When "strict-raw-types" is enabled, "raw types" must have type arguments.
-
-      A "raw type" is a type name that does not use inference to fill in missing
-      type arguments; instead, each type argument is instantiated to its bound.
-
-      Parameters:
-      0: the name of the generic type
-  SUBTYPE_OF_SEALED_CLASS:
-    problemMessage: "The class '{0}' shouldn't be extended, mixed in, or implemented because it's sealed."
-    correctionMessage: "Try composing instead of inheriting, or refer to the documentation of '{0}' for more information."
-    hasPublishedDocs: true
-    comment: |-
-      Parameters:
-      0: the name of the sealed class
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a sealed class (one that either
-      has the `[sealed][meta-sealed]` annotation or inherits or mixes in a
-      sealed class) is referenced in either the `extends`, `implements`, or
-      `with` clause of a class or mixin declaration if the declaration isn't in
-      the same package as the sealed class.
-
-      #### Example
-
-      Given a library in a package other than the package being analyzed that
-      contains the following:
-
-      ```dart
-      %uri="package:a/a.dart"
-      import 'package:meta/meta.dart';
-
-      class A {}
-
-      @sealed
-      class B {}
-      ```
-
-      The following code produces this diagnostic because `C`, which isn't in the
-      same package as `B`, is extending the sealed class `B`:
-
-      ```dart
-      import 'package:a/a.dart';
-
-      [!class C extends B {}!]
-      ```
-
-      #### Common fixes
-
-      If the class doesn't need to be a subtype of the sealed class, then change
-      the declaration so that it isn't:
-
-      ```dart
-      import 'package:a/a.dart';
-
-      class B extends A {}
-      ```
-
-      If the class needs to be a subtype of the sealed class, then either change
-      the sealed class so that it's no longer sealed or move the subclass into
-      the same package as the sealed class.
-  UNDEFINED_REFERENCED_PARAMETER:
-    problemMessage: "The parameter '{0}' isn't defined by '{1}'."
-    hasPublishedDocs: true
-    comment: |-
-      Parameters:
-      0: the name of the undefined parameter
-      1: the name of the targeted member
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when an annotation of the form
-      `[UseResult][meta-UseResult].unless(parameterDefined: parameterName)`
-      specifies a parameter name that isn't defined by the annotated function.
-
-      #### Example
-
-      The following code produces this diagnostic because the function `f`
-      doesn't have a parameter named `b`:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      @UseResult.unless(parameterDefined: [!'b'!])
-      int f([int? a]) => a ?? 0;
-      ```
-
-      #### Common fixes
-
-      Change the argument named `parameterDefined` to match the name of one of
-      the parameters to the function:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      @UseResult.unless(parameterDefined: 'a')
-      int f([int? a]) => a ?? 0;
-      ```
   UNIGNORABLE_IGNORE:
     problemMessage: "The diagnostic '{0}' can't be ignored."
     correctionMessage: Try removing the name from the list, or removing the whole comment if this is the only name in the list.
@@ -18755,14 +19667,14 @@
 
       #### Example
 
-      Given a file named `a.dart` that contains the following:
+      Given a file `a.dart` that contains the following:
 
       ```dart
       %uri="lib/a.dart"
       class A {}
       ```
 
-      And, given a file named `b.dart` that contains the following:
+      And, given a file `b.dart` that contains the following:
 
       ```dart
       %uri="lib/b.dart"
@@ -18789,241 +19701,47 @@
       If some of the names imported by this import are intended to be used but
       aren't yet, and if those names aren't imported by other imports, then add
       the missing references to those names.
-  UNNECESSARY_NAN_COMPARISON_FALSE:
-    sharedName: UNNECESSARY_NAN_COMPARISON
-    problemMessage: A double can't equal 'double.nan', so the condition is always 'false'.
-    correctionMessage: Try using 'double.isNan', or removing the condition.
-    hasPublishedDocs: false
-    comment: No parameters.
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a value is compared to
-      `double.nan` using either `==` or `!=`.
-
-      Dart follows the [IEEE 754] floating-point standard for the semantics of
-      floating point operations, which states that, for any floating point value
-      `x` (including NaN, positive infinity, and negative infinity),
-      - `NaN == x` is always false
-      - `NaN != x` is always true
-
-      As a result, comparing any value to NaN is pointless because the result is
-      already known (based on the comparison operator being used).
-
-      #### Example
-
-      The following code produces this diagnostic because `d` is being compared
-      to `double.nan`:
-
-      ```dart
-      bool isNaN(double d) => d [!== double.nan!];
-      ```
-
-      #### Common fixes
-
-      Use the getter `double.isNaN` instead:
-
-      ```dart
-      bool isNaN(double d) => d.isNaN;
-      ```
-  UNNECESSARY_NAN_COMPARISON_TRUE:
-    sharedName: UNNECESSARY_NAN_COMPARISON
-    problemMessage: A double can't equal 'double.nan', so the condition is always 'true'.
-    correctionMessage: Try using 'double.isNan', or removing the condition.
-    hasPublishedDocs: false
-    comment: No parameters.
-  UNNECESSARY_NO_SUCH_METHOD:
-    problemMessage: "Unnecessary 'noSuchMethod' declaration."
-    correctionMessage: "Try removing the declaration of 'noSuchMethod'."
-    hasPublishedDocs: true
-    comment: No parameters.
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when there's a declaration of
-      `noSuchMethod`, the only thing the declaration does is invoke the
-      overridden declaration, and the overridden declaration isn't the
-      declaration in `Object`.
-
-      Overriding the implementation of `Object`'s `noSuchMethod` (no matter what
-      the implementation does) signals to the analyzer that it shouldn't flag any
-      inherited abstract methods that aren't implemented in that class. This
-      works even if the overriding implementation is inherited from a superclass,
-      so there's no value to declare it again in a subclass.
-
-      #### Example
-
-      The following code produces this diagnostic because the declaration of
-      `noSuchMethod` in `A` makes the declaration of `noSuchMethod` in `B`
-      unnecessary:
-
-      ```dart
-      class A {
-        @override
-        dynamic noSuchMethod(x) => super.noSuchMethod(x);
-      }
-      class B extends A {
-        @override
-        dynamic [!noSuchMethod!](y) {
-          return super.noSuchMethod(y);
-        }
-      }
-      ```
-
-      #### Common fixes
-
-      Remove the unnecessary declaration:
-
-      ```dart
-      class A {
-        @override
-        dynamic noSuchMethod(x) => super.noSuchMethod(x);
-      }
-      class B extends A {}
-      ```
-  UNNECESSARY_NULL_COMPARISON_FALSE:
-    sharedName: UNNECESSARY_NULL_COMPARISON
-    problemMessage: "The operand can't be null, so the condition is always 'false'."
-    correctionMessage: Try removing the condition, an enclosing condition, or the whole conditional statement.
-    hasPublishedDocs: true
-    comment: No parameters.
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when it finds an equality comparison
-      (either `==` or `!=`) with one operand of `null` and the other operand
-      can't be `null`. Such comparisons are always either `true` or `false`, so
-      they serve no purpose.
-
-      #### Examples
-
-      The following code produces this diagnostic because `x` can never be
-      `null`, so the comparison always evaluates to `true`:
-
-      ```dart
-      void f(int x) {
-        if (x [!!= null!]) {
-          print(x);
-        }
-      }
-      ```
-
-      The following code produces this diagnostic because `x` can never be
-      `null`, so the comparison always evaluates to `false`:
-
-      ```dart
-      void f(int x) {
-        if (x [!== null!]) {
-          throw ArgumentError("x can't be null");
-        }
-      }
-      ```
-
-      #### Common fixes
-
-      If the other operand should be able to be `null`, then change the type of
-      the operand:
-
-      ```dart
-      void f(int? x) {
-        if (x != null) {
-          print(x);
-        }
-      }
-      ```
-
-      If the other operand really can't be `null`, then remove the condition:
-
-      ```dart
-      void f(int x) {
-        print(x);
-      }
-      ```
-  UNNECESSARY_NULL_COMPARISON_TRUE:
-    sharedName: UNNECESSARY_NULL_COMPARISON
-    problemMessage: "The operand can't be null, so the condition is always 'true'."
-    correctionMessage: Remove the condition.
-    hasPublishedDocs: true
-    comment: No parameters.
-  UNNECESSARY_QUESTION_MARK:
-    problemMessage: "The '?' is unnecessary because '{0}' is nullable without it."
-    hasPublishedDocs: true
-    comment: |-
-      Parameters:
-      0: the name of the type
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when either the type `dynamic` or the
-      type `Null` is followed by a question mark. Both of these types are
-      inherently nullable so the question mark doesn't change the semantics.
-
-      #### Example
-
-      The following code produces this diagnostic because the question mark
-      following `dynamic` isn't necessary:
-
-      ```dart
-      dynamic[!?!] x;
-      ```
-
-      #### Common fixes
-
-      Remove the unneeded question mark:
-
-      ```dart
-      dynamic x;
-      ```
-  UNNECESSARY_SET_LITERAL:
-    problemMessage: Braces unnecessarily wrap this expression in a set literal.
-    correctionMessage: Try removing the set literal around the expression.
-    hasPublishedDocs: false
-    comment: No parameters.
-  UNNECESSARY_TYPE_CHECK_FALSE:
-    sharedName: UNNECESSARY_TYPE_CHECK
-    problemMessage: "Unnecessary type check; the result is always 'false'."
-    correctionMessage: Try correcting the type check, or removing the type check.
-    hasPublishedDocs: true
-    comment: No parameters.
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when the value of a type check (using
-      either `is` or `is!`) is known at compile time.
-
-      #### Example
-
-      The following code produces this diagnostic because the test `a is Object?`
-      is always `true`:
-
-      ```dart
-      bool f<T>(T a) => [!a is Object?!];
-      ```
-
-      #### Common fixes
-
-      If the type check doesn't check what you intended to check, then change the
-      test:
-
-      ```dart
-      bool f<T>(T a) => a is Object;
-      ```
-
-      If the type check does check what you intended to check, then replace the
-      type check with its known value or completely remove it:
-
-      ```dart
-      bool f<T>(T a) => true;
-      ```
-  UNNECESSARY_TYPE_CHECK_TRUE:
-    sharedName: UNNECESSARY_TYPE_CHECK
-    problemMessage: "Unnecessary type check; the result is always 'true'."
-    correctionMessage: Try correcting the type check, or removing the type check.
-    hasPublishedDocs: true
-    comment: No parameters.
   UNREACHABLE_SWITCH_CASE:
     problemMessage: "This case is covered by the previous cases."
+    correctionMessage: Try removing the case clause, or restructuring the preceding patterns.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a `case` clause in a `switch`
+      statement doesn't match anything because all of the matchable values are
+      matched by an earlier `case` clause.
+
+      #### Example
+
+      The following code produces this diagnostic because the value `1` was
+      matched in the preceeding case:
+
+      ```dart
+      void f(int x) {
+        switch (x) {
+          case 1:
+            print('one');
+          [!case!] 1:
+            print('two');
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      Change one or both of the conflicting cases to match different values:
+
+      ```dart
+      void f(int x) {
+        switch (x) {
+          case 1:
+            print('one');
+          case 2:
+            print('two');
+        }
+      }
+      ```
   UNUSED_ELEMENT:
     problemMessage: "The declaration '{0}' isn't referenced."
     correctionMessage: "Try removing the declaration of '{0}'."
@@ -19162,57 +19880,6 @@
 
       If some of the imported names are intended to be used, then add the missing
       code.
-  UNUSED_LABEL:
-    problemMessage: "The label '{0}' isn't used."
-    correctionMessage: "Try removing the label, or using it in either a 'break' or 'continue' statement."
-    hasPublishedDocs: true
-    comment: |-
-      Parameters:
-      0: the label that isn't used
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a label that isn't used is
-      found.
-
-      #### Example
-
-      The following code produces this diagnostic because the label `loop` isn't
-      referenced anywhere in the method:
-
-      ```dart
-      void f(int limit) {
-        [!loop:!] for (int i = 0; i < limit; i++) {
-          print(i);
-        }
-      }
-      ```
-
-      #### Common fixes
-
-      If the label isn't needed, then remove it:
-
-      ```dart
-      void f(int limit) {
-        for (int i = 0; i < limit; i++) {
-          print(i);
-        }
-      }
-      ```
-
-      If the label is needed, then use it:
-
-      ```dart
-      void f(int limit) {
-        loop: for (int i = 0; i < limit; i++) {
-          print(i);
-          if (i != 0) {
-            break loop;
-          }
-        }
-      }
-      ```
-      TODO(brianwilkerson) Highlight the identifier without the colon.
   UNUSED_LOCAL_VARIABLE:
     problemMessage: "The value of the local variable '{0}' isn't used."
     correctionMessage: Try removing the variable or using it.
@@ -19242,93 +19909,6 @@
       If the variable isn't needed, then remove it.
 
       If the variable was intended to be used, then add the missing code.
-  UNUSED_RESULT:
-    problemMessage: "The value of '{0}' should be used."
-    correctionMessage: Try using the result by invoking a member, passing it to a function, or returning it from this function.
-    hasPublishedDocs: true
-    comment: |-
-      Parameters:
-      0: the name of the annotated method, property or function
-    documentation: |-
-      #### Description
-
-      The analyzer produces this diagnostic when a function annotated with
-      `[useResult][meta-useResult]` is invoked, and the value returned by that
-      function isn't used. The value is considered to be used if a member of the
-      value is invoked, if the value is passed to another function, or if the
-      value is assigned to a variable or field.
-
-      #### Example
-
-      The following code produces this diagnostic because the invocation of
-      `c.a()` isn't used, even though the method `a` is annotated with
-      `[useResult][meta-useResult]`:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      class C {
-        @useResult
-        int a() => 0;
-
-        int b() => 0;
-      }
-
-      void f(C c) {
-        c.[!a!]();
-      }
-      ```
-
-      #### Common fixes
-
-      If you intended to invoke the annotated function, then use the value that
-      was returned:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      class C {
-        @useResult
-        int a() => 0;
-
-        int b() => 0;
-      }
-
-      void f(C c) {
-        print(c.a());
-      }
-      ```
-
-      If you intended to invoke a different function, then correct the name of
-      the function being invoked:
-
-      ```dart
-      import 'package:meta/meta.dart';
-
-      class C {
-        @useResult
-        int a() => 0;
-
-        int b() => 0;
-      }
-
-      void f(C c) {
-        c.b();
-      }
-      ```
-  UNUSED_RESULT_WITH_MESSAGE:
-    sharedName: UNUSED_RESULT
-    problemMessage: "'{0}' should be used. {1}."
-    correctionMessage: Try using the result by invoking a member, passing it to a function, or returning it from this function.
-    hasPublishedDocs: true
-    comment: |-
-      The result of invoking a method, property, or function annotated with
-      `@useResult` must be used (assigned, passed to a function as an argument,
-      or returned by a function).
-
-      Parameters:
-      0: the name of the annotated method, property or function
-      1: message details
   UNUSED_SHOWN_NAME:
     problemMessage: "The name {0} is shown, but isn't used."
     correctionMessage: Try removing the name from the list of shown members.
@@ -20229,7 +20809,7 @@
 
       #### Example
 
-      Assuming that the directory `local_package` doesn't contain a file named
+      Assuming that the directory `local_package` doesn't contain a file
       `pubspec.yaml`, the following code produces this diagnostic because it's
       listed as the path of a package:
 
@@ -20560,10 +21140,63 @@
     problemMessage: The null-assert pattern will have no effect because the matched type isn't nullable.
     correctionMessage: Try replacing the null-assert pattern with its nested pattern.
     comment: No parameters.
+
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a null-assert pattern is used
+      to match a value that isn't nullable.
+
+      #### Example
+
+      The following code produces this diagnostic because the variable `x` isn't
+      nullable:
+
+      ```dart
+      void f(int x) {
+        if (x case var a[!!!] when a > 0) {}
+      }
+      ```
+
+      #### Common fixes
+
+      Remove the null-assert pattern:
+
+      ```dart
+      void f(int x) {
+        if (x case var a when a > 0) {}
+      }
+      ```
   UNNECESSARY_NULL_CHECK_PATTERN:
     problemMessage: The null-check pattern will have no effect because the matched type isn't nullable.
     correctionMessage: Try replacing the null-check pattern with its nested pattern.
     comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a null-check pattern is used to
+      match a value that isn't nullable.
+
+      #### Example
+
+      The following code produces this diagnostic because the value `x` isn't
+      nullable:
+
+      ```dart
+      void f(int x) {
+        if (x case var a[!?!] when a > 0) {}
+      }
+      ```
+
+      #### Common fixes
+
+      Remove the null-check pattern:
+
+      ```dart
+      void f(int x) {
+        if (x case var a when a > 0) {}
+      }
+      ```
 WarningCode:
   ARGUMENT_TYPE_NOT_ASSIGNABLE_TO_ERROR_HANDLER:
     problemMessage: "The argument type '{0}' can't be assigned to the parameter type '{1} Function(Object)' or '{1} Function(Object, StackTrace)'."
@@ -20625,6 +21258,41 @@
         f.catchError((Object error) => 0);
       }
       ```
+  ASSIGNMENT_OF_DO_NOT_STORE:
+    problemMessage: "'{0}' is marked 'doNotStore' and shouldn't be assigned to a field or top-level variable."
+    correctionMessage: Try removing the assignment.
+    comment: |-
+      Users should not assign values marked `@doNotStore`.
+
+      Parameters:
+      0: the name of the field or variable
+    hasPublishedDocs: true
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when the value of a function
+      (including methods and getters) that is explicitly or implicitly marked by
+      the `[doNotStore][meta-doNotStore]` annotation is stored in either a field
+      or top-level variable.
+
+      #### Example
+
+      The following code produces this diagnostic because the value of the
+      function `f` is being stored in the top-level variable `x`:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      @doNotStore
+      int f() => 1;
+
+      var x = [!f()!];
+      ```
+
+      #### Common fixes
+
+      Replace references to the field or variable with invocations of the
+      function producing the value.
   BODY_MIGHT_COMPLETE_NORMALLY_CATCH_ERROR:
     problemMessage: "This 'onError' handler must return a value assignable to '{0}', but ends without returning a value."
     correctionMessage: Try adding a return statement.
@@ -20802,9 +21470,213 @@
       Parameters:
       0: the matched value type
       1: the constant value type
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a constant pattern can never
+      match the value it's being tested against because the type of the constant
+      is known to never match the type of the value.
+
+      #### Example
+
+      The following code produces this diagnostic because the type of the
+      constant pattern `(true)` is `bool`, and the type of the value being
+      matched (`x`) is `int`, and a Boolean can never match an integer:
+
+      ```dart
+      void f(int x) {
+        if (x case [!true!]) {}
+      }
+      ```
+
+      #### Common fixes
+
+      If the type of the value is correct, then rewrite the pattern to be
+      compatible:
+
+      ```dart
+      void f(int x) {
+        if (x case 3) {}
+      }
+      ```
+
+      If the type of the constant is correct, then rewrite the value to be
+      compatible:
+
+      ```dart
+      void f(bool x) {
+        if (x case true) {}
+      }
+      ```
   DEAD_CODE:
-    aliasFor: HintCode.DEAD_CODE
-    comment: This is the new replacement for [HintCode.DEAD_CODE].
+    problemMessage: Dead code.
+    correctionMessage: Try removing the code, or fixing the code before it so that it can be reached.
+    hasPublishedDocs: true
+    comment: |-
+      Dead code is code that is never reached, this can happen for instance if a
+      statement follows a return statement.
+
+      No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when code is found that won't be
+      executed because execution will never reach the code.
+
+      #### Example
+
+      The following code produces this diagnostic because the invocation of
+      `print` occurs after the function has returned:
+
+      ```dart
+      void f() {
+        return;
+        [!print('here');!]
+      }
+      ```
+
+      #### Common fixes
+
+      If the code isn't needed, then remove it:
+
+      ```dart
+      void f() {
+        return;
+      }
+      ```
+
+      If the code needs to be executed, then either move the code to a place
+      where it will be executed:
+
+      ```dart
+      void f() {
+        print('here');
+        return;
+      }
+      ```
+
+      Or, rewrite the code before it, so that it can be reached:
+
+      ```dart
+      void f({bool skipPrinting = true}) {
+        if (skipPrinting) {
+          return;
+        }
+        print('here');
+      }
+      ```
+  DEAD_CODE_CATCH_FOLLOWING_CATCH:
+    problemMessage: "Dead code: Catch clauses after a 'catch (e)' or an 'on Object catch (e)' are never reached."
+    correctionMessage: Try reordering the catch clauses so that they can be reached, or removing the unreachable catch clauses.
+    hasPublishedDocs: true
+    comment: |-
+      Dead code is code that is never reached. This case covers cases where the
+      user has catch clauses after `catch (e)` or `on Object catch (e)`.
+
+      No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a `catch` clause is found that
+      can't be executed because it's after a `catch` clause of the form
+      `catch (e)` or `on Object catch (e)`. The first `catch` clause that matches
+      the thrown object is selected, and both of those forms will match any
+      object, so no `catch` clauses that follow them will be selected.
+
+      #### Example
+
+      The following code produces this diagnostic:
+
+      ```dart
+      void f() {
+        try {
+        } catch (e) {
+        } [!on String {
+        }!]
+      }
+      ```
+
+      #### Common fixes
+
+      If the clause should be selectable, then move the clause before the general
+      clause:
+
+      ```dart
+      void f() {
+        try {
+        } on String {
+        } catch (e) {
+        }
+      }
+      ```
+
+      If the clause doesn't need to be selectable, then remove it:
+
+      ```dart
+      void f() {
+        try {
+        } catch (e) {
+        }
+      }
+      ```
+  DEAD_CODE_ON_CATCH_SUBTYPE:
+    problemMessage: "Dead code: This on-catch block won't be executed because '{0}' is a subtype of '{1}' and hence will have been caught already."
+    correctionMessage: Try reordering the catch clauses so that this block can be reached, or removing the unreachable catch clause.
+    hasPublishedDocs: true
+    comment: |-
+      Dead code is code that is never reached. This case covers cases where the
+      user has an on-catch clause such as `on A catch (e)`, where a supertype of
+      `A` was already caught.
+
+      Parameters:
+      0: name of the subtype
+      1: name of the supertype
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a `catch` clause is found that
+      can't be executed because it is after a `catch` clause that catches either
+      the same type or a supertype of the clause's type. The first `catch` clause
+      that matches the thrown object is selected, and the earlier clause always
+      matches anything matchable by the highlighted clause, so the highlighted
+      clause will never be selected.
+
+      #### Example
+
+      The following code produces this diagnostic:
+
+      ```dart
+      void f() {
+        try {
+        } on num {
+        } [!on int {
+        }!]
+      }
+      ```
+
+      #### Common fixes
+
+      If the clause should be selectable, then move the clause before the general
+      clause:
+
+      ```dart
+      void f() {
+        try {
+        } on int {
+        } on num {
+        }
+      }
+      ```
+
+      If the clause doesn't need to be selectable, then remove it:
+
+      ```dart
+      void f() {
+        try {
+        } on num {
+        }
+      }
+      ```
   DEPRECATED_EXTENDS_FUNCTION:
     sharedName: DEPRECATED_SUBTYPE_OF_FUNCTION
     problemMessage: "Extending 'Function' is deprecated."
@@ -21303,7 +22175,7 @@
 
       #### Example
 
-      Given a file named `a.dart` in the `src` directory that contains:
+      Given a file `a.dart` in the `src` directory that contains:
 
       ```dart
       %uri="lib/src/a.dart"
@@ -21347,7 +22219,7 @@
 
       #### Example
 
-      Given a file named `a.dart` in the `src` directory that contains the
+      Given a file `a.dart` in the `src` directory that contains the
       following:
 
       ```dart
@@ -21379,8 +22251,8 @@
   INVALID_FACTORY_ANNOTATION:
     problemMessage: Only methods can be annotated as factories.
     comment: |-
-      This hint is generated anywhere a @factory annotation is associated with
-      anything other than a method.
+      This warning is generated anywhere a @factory annotation is associated
+      with anything other than a method.
   INVALID_FACTORY_METHOD_DECL:
     problemMessage: "Factory method '{0}' must have a return type."
     comment: |-
@@ -21472,8 +22344,8 @@
   INVALID_IMMUTABLE_ANNOTATION:
     problemMessage: Only classes can be annotated as being immutable.
     comment: |-
-      This hint is generated anywhere an @immutable annotation is associated with
-      anything other than a class.
+      This warning is generated anywhere an @immutable annotation is associated
+      with anything other than a class.
   INVALID_INTERNAL_ANNOTATION:
     problemMessage: "Only public elements in a package's private API can be annotated as being internal."
     comment: |-
@@ -21682,7 +22554,7 @@
     correctionMessage: Try removing '@nonVirtual'.
     hasPublishedDocs: true
     comment: |-
-      This hint is generated anywhere where `@nonVirtual` annotates something
+      This warning is generated anywhere where `@nonVirtual` annotates something
       other than a non-abstract instance member in a class or mixin.
 
       No Parameters.
@@ -21762,7 +22634,7 @@
     problemMessage: "The member '{0}' is declared non-virtual in '{1}' and can't be overridden in subclasses."
     hasPublishedDocs: true
     comment: |-
-      This hint is generated anywhere where an instance member annotated with
+      This warning is generated anywhere where an instance member annotated with
       `@nonVirtual` is overridden in a subclass.
 
       Parameters:
@@ -21828,7 +22700,7 @@
     problemMessage: "The type parameter '{0}' is annotated with @required but only named parameters without a default value can be annotated with it."
     correctionMessage: Remove @required.
     comment: |-
-      This hint is generated anywhere where `@required` annotates a named
+      This warning is generated anywhere where `@required` annotates a named
       parameter with a default value.
 
       Parameters:
@@ -21837,7 +22709,7 @@
     problemMessage: "Incorrect use of the annotation @required on the optional positional parameter '{0}'. Optional positional parameters cannot be required."
     correctionMessage: Remove @required.
     comment: |-
-      This hint is generated anywhere where `@required` annotates an optional
+      This warning is generated anywhere where `@required` annotates an optional
       positional parameter.
 
       Parameters:
@@ -21846,8 +22718,8 @@
     problemMessage: "Redundant use of the annotation @required on the required positional parameter '{0}'."
     correctionMessage: Remove @required.
     comment: |-
-      This hint is generated anywhere where `@required` annotates a non optional
-      positional parameter.
+      This warning is generated anywhere where `@required` annotates a
+      non-optional positional parameter.
 
       Parameters:
       0: the name of the member
@@ -21892,8 +22764,8 @@
   INVALID_USE_OF_PROTECTED_MEMBER:
     problemMessage: "The member '{0}' can only be used within instance members of subclasses of '{1}'."
     comment: |-
-      This hint is generated anywhere where a member annotated with `@protected`
-      is used outside of an instance member of a subclass.
+      This warning is generated anywhere where a member annotated with
+      `@protected` is used outside of an instance member of a subclass.
 
       Parameters:
       0: the name of the member
@@ -21914,7 +22786,7 @@
 
       #### Example
 
-      Given a file named `a.dart` containing the following declaration:
+      Given a file `a.dart` containing the following declaration:
 
       ```dart
       %uri="lib/a.dart"
@@ -21946,7 +22818,7 @@
   INVALID_USE_OF_VISIBLE_FOR_TEMPLATE_MEMBER:
     problemMessage: "The member '{0}' can only be used within '{1}' or a template library."
     comment: |-
-      This hint is generated anywhere where a member annotated with
+      This warning is generated anywhere where a member annotated with
       `@visibleForTemplate` is used outside of a "template" Dart file.
 
       Parameters:
@@ -21956,7 +22828,7 @@
     problemMessage: "The member '{0}' can only be used within '{1}' or a test."
     hasPublishedDocs: true
     comment: |-
-      This hint is generated anywhere where a member annotated with
+      This warning is generated anywhere where a member annotated with
       `@visibleForTesting` is used outside the defining library, or a test.
 
       Parameters:
@@ -21971,7 +22843,7 @@
 
       #### Example
 
-      Given a file named `c.dart` that contains the following:
+      Given a file `c.dart` that contains the following:
 
       ```dart
       %uri="lib/c.dart"
@@ -22018,8 +22890,8 @@
     problemMessage: "The member '{0}' is annotated with '{1}', but this annotation is only meaningful on declarations of public members."
     hasPublishedDocs: true
     comment: |-
-      This hint is generated anywhere where a private declaration is annotated
-      with `@visibleForTemplate` or `@visibleForTesting`.
+      This warning is generated anywhere where a private declaration is
+      annotated with `@visibleForTemplate` or `@visibleForTesting`.
 
       Parameters:
       0: the name of the member
@@ -22168,8 +23040,8 @@
     correctionMessage: Try removing the '@sealed' annotation.
     hasPublishedDocs: true
     comment: |-
-      This hint is generated anywhere where `@sealed` annotates something other
-      than a class.
+      This warning is generated anywhere where `@sealed` annotates something
+      other than a class.
 
       No parameters.
     documentation: |-
@@ -22284,8 +23156,8 @@
     problemMessage: "The parameter '{0}' is required."
     hasPublishedDocs: true
     comment: |-
-      Generate a hint for a constructor, function or method invocation where a
-      required parameter is missing.
+      Generates a warning for a constructor, function or method invocation where
+      a required parameter is missing.
 
       Parameters:
       0: the name of the parameter
@@ -22331,8 +23203,8 @@
     problemMessage: "The parameter '{0}' is required. {1}."
     hasPublishedDocs: true
     comment: |-
-      Generate a hint for a constructor, function or method invocation where a
-      required parameter is missing.
+      Generates a warning for a constructor, function or method invocation where
+      a required parameter is missing.
 
       Parameters:
       0: the name of the parameter
@@ -22369,11 +23241,55 @@
 
       Add a `return` statement that makes the return value explicit, even if
       `null` is the appropriate value.
+  MIXIN_ON_SEALED_CLASS:
+    problemMessage: "The class '{0}' shouldn't be used as a mixin constraint because it is sealed, and any class mixing in this mixin must have '{0}' as a superclass."
+    correctionMessage: Try composing with this class, or refer to its documentation for more information.
+    hasPublishedDocs: true
+    comment: |-
+      This warning is generated anywhere where a `@sealed` class is used as a
+      a superclass constraint of a mixin.
+
+      Parameters:
+      0: the name of the sealed class
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when the superclass constraint of a
+      mixin is a class from a different package that was marked as
+      `[sealed][meta-sealed]`. Classes that are sealed can't be extended,
+      implemented, mixed in, or used as a superclass constraint.
+
+      #### Example
+
+      If the package `p` defines a sealed class:
+
+      ```dart
+      %uri="package:p/p.dart"
+      import 'package:meta/meta.dart';
+
+      @sealed
+      class C {}
+      ```
+
+      Then, the following code, when in a package other than `p`, produces this
+      diagnostic:
+
+      ```dart
+      import 'package:p/p.dart';
+
+      [!mixin M on C {}!]
+      ```
+
+      #### Common fixes
+
+      If the classes that use the mixin don't need to be subclasses of the sealed
+      class, then consider adding a field and delegating to the wrapped instance
+      of the sealed class.
   MUST_BE_IMMUTABLE:
     problemMessage: "This class (or a class that this class inherits from) is marked as '@immutable', but one or more of its instance fields aren't final: {0}"
     hasPublishedDocs: true
     comment: |-
-      Generate a hint for classes that inherit from classes annotated with
+      Generates a warning for classes that inherit from classes annotated with
       `@immutable` but that are not immutable.
 
       Parameters:
@@ -22480,9 +23396,65 @@
         }
       }
       ```
+  NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW:
+    sharedName: NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR
+    problemMessage: "This instance creation must be 'const', because the {0} constructor is marked as '@literal'."
+    correctionMessage: "Try replacing the 'new' keyword with 'const'."
+    hasPublishedDocs: true
+    comment: |-
+      Generate a warning for non-const instance creation (with the `new` keyword)
+      using a constructor annotated with `@literal`.
+
+      Parameters:
+      0: the name of the class defining the annotated constructor
   NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR:
-    aliasFor: HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR
-    comment: This is the new replacement for [HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR].
+    problemMessage: "This instance creation must be 'const', because the {0} constructor is marked as '@literal'."
+    correctionMessage: "Try adding a 'const' keyword."
+    hasPublishedDocs: true
+    comment: |-
+      Generates a warning for non-const instance creation using a constructor
+      annotated with `@literal`.
+
+      Parameters:
+      0: the name of the class defining the annotated constructor
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a constructor that has the
+      `[literal][meta-literal]` annotation is invoked without using the `const`
+      keyword, but all of the arguments to the constructor are constants. The
+      annotation indicates that the constructor should be used to create a
+      constant value whenever possible.
+
+      #### Example
+
+      The following code produces this diagnostic:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      class C {
+        @literal
+        const C();
+      }
+
+      C f() => [!C()!];
+      ```
+
+      #### Common fixes
+
+      Add the keyword `const` before the constructor invocation:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      class C {
+        @literal
+        const C();
+      }
+
+      void f() => const C();
+      ```
   NULLABLE_TYPE_IN_CATCH_CLAUSE:
     problemMessage: "A potentially nullable type can't be used in an 'on' clause because it isn't valid to throw a nullable expression."
     correctionMessage: Try using a non-nullable type.
@@ -22576,9 +23548,117 @@
     comment: |-
       A condition in operands of a logical operator could evaluate to `null`
       because it uses the null-aware '?.' operator.
+  NULL_CHECK_ALWAYS_FAILS:
+    problemMessage: "This null-check will always throw an exception because the expression will always evaluate to 'null'."
+    comment: |-
+      No parameters.
+    hasPublishedDocs: true
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when the null check operator (`!`)
+      is used on an expression whose value can only be `null`. In such a case
+      the operator always throws an exception, which likely isn't the intended
+      behavior.
+
+      #### Example
+
+      The following code produces this diagnostic because the function `g` will
+      always return `null`, which means that the null check in `f` will always
+      throw:
+
+      ```dart
+      void f() {
+        [!g()!!];
+      }
+
+      Null g() => null;
+      ```
+
+      #### Common fixes
+
+      If you intend to always throw an exception, then replace the null check
+      with an explicit `throw` expression to make the intent more clear:
+
+      ```dart
+      void f() {
+        g();
+        throw TypeError();
+      }
+
+      Null g() => null;
+      ```
   OVERRIDE_ON_NON_OVERRIDING_FIELD:
-    aliasFor: HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD
-    comment: This is the new replacement for [HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD].
+    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
+    problemMessage: "The field doesn't override an inherited getter or setter."
+    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
+    hasPublishedDocs: true
+    comment: |-
+      A field with the override annotation does not override a getter or setter.
+
+      No parameters.
+  OVERRIDE_ON_NON_OVERRIDING_GETTER:
+    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
+    problemMessage: "The getter doesn't override an inherited getter."
+    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
+    hasPublishedDocs: true
+    comment: |-
+      A getter with the override annotation does not override an existing getter.
+
+      No parameters.
+  OVERRIDE_ON_NON_OVERRIDING_METHOD:
+    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
+    problemMessage: "The method doesn't override an inherited method."
+    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
+    hasPublishedDocs: true
+    comment: |-
+      A method with the override annotation does not override an existing method.
+
+      No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a class member is annotated with
+      the `@override` annotation, but the member isn't declared in any of the
+      supertypes of the class.
+
+      #### Example
+
+      The following code produces this diagnostic because `m` isn't declared in
+      any of the supertypes of `C`:
+
+      ```dart
+      class C {
+        @override
+        String [!m!]() => '';
+      }
+      ```
+
+      #### Common fixes
+
+      If the member is intended to override a member with a different name, then
+      update the member to have the same name:
+
+      ```dart
+      class C {
+        @override
+        String toString() => '';
+      }
+      ```
+
+      If the member is intended to override a member that was removed from the
+      superclass, then consider removing the member from the subclass.
+
+      If the member can't be removed, then remove the annotation.
+  OVERRIDE_ON_NON_OVERRIDING_SETTER:
+    sharedName: OVERRIDE_ON_NON_OVERRIDING_MEMBER
+    problemMessage: "The setter doesn't override an inherited setter."
+    correctionMessage: Try updating this class to match the superclass, or removing the override annotation.
+    hasPublishedDocs: true
+    comment: |-
+      A setter with the override annotation does not override an existing setter.
+
+      No parameters.
   RECEIVER_OF_TYPE_NEVER:
     problemMessage: "The receiver is of type 'Never', and will never complete with a value."
     correctionMessage: Try checking for throw expressions or type errors in the receiver
@@ -22595,6 +23675,9 @@
       the call are unreachable.
 
       Parameters: none
+  RECORD_LITERAL_ONE_POSITIONAL_NO_TRAILING_COMMA:
+    problemMessage: "A record literal with exactly one positional field requires a trailing comma."
+    correctionMessage: Try adding a trailing comma.
   REMOVED_LINT_USE:
     problemMessage: "'{0}' was removed in Dart '{1}'"
     correctionMessage: "Remove the reference to '{0}'."
@@ -23198,6 +24281,13 @@
       ```dart
       var s = new Set<int>();
       ```
+  SDK_VERSION_SINCE:
+    problemMessage: "This API is available since SDK {0}, but constraints '{1}' don't guarantee it."
+    correctionMessage: Try updating the SDK constraints.
+    comment: |-
+      Parameters:
+      0: the version specified in the `@Since()` annotation
+      1: the SDK version constraints
   SDK_VERSION_UI_AS_CODE:
     problemMessage: "The for, if, and spread elements weren't supported until version 2.3.0, but this code is required to be able to run on earlier versions."
     correctionMessage: Try updating the SDK constraints.
@@ -23312,6 +24402,71 @@
       const a = [1, 2];
       var b = [...a];
       ```
+  STRICT_RAW_TYPE:
+    problemMessage: "The generic type '{0}' should have explicit type arguments but doesn't."
+    correctionMessage: "Use explicit type arguments for '{0}'."
+    comment: |-
+      When "strict-raw-types" is enabled, "raw types" must have type arguments.
+
+      A "raw type" is a type name that does not use inference to fill in missing
+      type arguments; instead, each type argument is instantiated to its bound.
+
+      Parameters:
+      0: the name of the generic type
+  SUBTYPE_OF_SEALED_CLASS:
+    problemMessage: "The class '{0}' shouldn't be extended, mixed in, or implemented because it's sealed."
+    correctionMessage: "Try composing instead of inheriting, or refer to the documentation of '{0}' for more information."
+    hasPublishedDocs: true
+    comment: |-
+      Parameters:
+      0: the name of the sealed class
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a sealed class (one that either
+      has the `[sealed][meta-sealed]` annotation or inherits or mixes in a
+      sealed class) is referenced in either the `extends`, `implements`, or
+      `with` clause of a class or mixin declaration if the declaration isn't in
+      the same package as the sealed class.
+
+      #### Example
+
+      Given a library in a package other than the package being analyzed that
+      contains the following:
+
+      ```dart
+      %uri="package:a/a.dart"
+      import 'package:meta/meta.dart';
+
+      class A {}
+
+      @sealed
+      class B {}
+      ```
+
+      The following code produces this diagnostic because `C`, which isn't in the
+      same package as `B`, is extending the sealed class `B`:
+
+      ```dart
+      import 'package:a/a.dart';
+
+      [!class C extends B {}!]
+      ```
+
+      #### Common fixes
+
+      If the class doesn't need to be a subtype of the sealed class, then change
+      the declaration so that it isn't:
+
+      ```dart
+      import 'package:a/a.dart';
+
+      class B extends A {}
+      ```
+
+      If the class needs to be a subtype of the sealed class, then either change
+      the sealed class so that it's no longer sealed or move the subclass into
+      the same package as the sealed class.
   TEXT_DIRECTION_CODE_POINT_IN_COMMENT:
     problemMessage: The Unicode code point 'U+{0}' changes the appearance of text from how it's interpreted by the compiler.
     correctionMessage: Try removing the code point or using the Unicode escape sequence '\u{0}'.
@@ -23485,6 +24640,43 @@
 
       var x = min(0, 1);
       ```
+  UNDEFINED_REFERENCED_PARAMETER:
+    problemMessage: "The parameter '{0}' isn't defined by '{1}'."
+    hasPublishedDocs: true
+    comment: |-
+      Parameters:
+      0: the name of the undefined parameter
+      1: the name of the targeted member
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when an annotation of the form
+      `[UseResult][meta-UseResult].unless(parameterDefined: parameterName)`
+      specifies a parameter name that isn't defined by the annotated function.
+
+      #### Example
+
+      The following code produces this diagnostic because the function `f`
+      doesn't have a parameter named `b`:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      @UseResult.unless(parameterDefined: [!'b'!])
+      int f([int? a]) => a ?? 0;
+      ```
+
+      #### Common fixes
+
+      Change the argument named `parameterDefined` to match the name of one of
+      the parameters to the function:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      @UseResult.unless(parameterDefined: 'a')
+      int f([int? a]) => a ?? 0;
+      ```
   UNDEFINED_SHOWN_NAME:
     problemMessage: "The library '{0}' doesn't export a member with the shown name '{1}'."
     correctionMessage: Try removing the name from the list of shown members.
@@ -23530,16 +24722,282 @@
   UNNECESSARY_FINAL:
     aliasFor: HintCode.UNNECESSARY_FINAL
     comment: This is the new replacement for [HintCode.UNNECESSARY_FINAL].
+  UNNECESSARY_NAN_COMPARISON_FALSE:
+    sharedName: UNNECESSARY_NAN_COMPARISON
+    problemMessage: A double can't equal 'double.nan', so the condition is always 'false'.
+    correctionMessage: Try using 'double.isNan', or removing the condition.
+    hasPublishedDocs: false
+    comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a value is compared to
+      `double.nan` using either `==` or `!=`.
+
+      Dart follows the [IEEE 754] floating-point standard for the semantics of
+      floating point operations, which states that, for any floating point value
+      `x` (including NaN, positive infinity, and negative infinity),
+      - `NaN == x` is always false
+      - `NaN != x` is always true
+
+      As a result, comparing any value to NaN is pointless because the result is
+      already known (based on the comparison operator being used).
+
+      #### Example
+
+      The following code produces this diagnostic because `d` is being compared
+      to `double.nan`:
+
+      ```dart
+      bool isNaN(double d) => d [!== double.nan!];
+      ```
+
+      #### Common fixes
+
+      Use the getter `double.isNaN` instead:
+
+      ```dart
+      bool isNaN(double d) => d.isNaN;
+      ```
+  UNNECESSARY_NAN_COMPARISON_TRUE:
+    sharedName: UNNECESSARY_NAN_COMPARISON
+    problemMessage: A double can't equal 'double.nan', so the condition is always 'true'.
+    correctionMessage: Try using 'double.isNan', or removing the condition.
+    hasPublishedDocs: false
+    comment: No parameters.
+  UNNECESSARY_NO_SUCH_METHOD:
+    problemMessage: "Unnecessary 'noSuchMethod' declaration."
+    correctionMessage: "Try removing the declaration of 'noSuchMethod'."
+    hasPublishedDocs: true
+    comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when there's a declaration of
+      `noSuchMethod`, the only thing the declaration does is invoke the
+      overridden declaration, and the overridden declaration isn't the
+      declaration in `Object`.
+
+      Overriding the implementation of `Object`'s `noSuchMethod` (no matter what
+      the implementation does) signals to the analyzer that it shouldn't flag any
+      inherited abstract methods that aren't implemented in that class. This
+      works even if the overriding implementation is inherited from a superclass,
+      so there's no value to declare it again in a subclass.
+
+      #### Example
+
+      The following code produces this diagnostic because the declaration of
+      `noSuchMethod` in `A` makes the declaration of `noSuchMethod` in `B`
+      unnecessary:
+
+      ```dart
+      class A {
+        @override
+        dynamic noSuchMethod(x) => super.noSuchMethod(x);
+      }
+      class B extends A {
+        @override
+        dynamic [!noSuchMethod!](y) {
+          return super.noSuchMethod(y);
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      Remove the unnecessary declaration:
+
+      ```dart
+      class A {
+        @override
+        dynamic noSuchMethod(x) => super.noSuchMethod(x);
+      }
+      class B extends A {}
+      ```
+  UNNECESSARY_NULL_COMPARISON_FALSE:
+    sharedName: UNNECESSARY_NULL_COMPARISON
+    problemMessage: "The operand can't be null, so the condition is always 'false'."
+    correctionMessage: Try removing the condition, an enclosing condition, or the whole conditional statement.
+    hasPublishedDocs: true
+    comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when it finds an equality comparison
+      (either `==` or `!=`) with one operand of `null` and the other operand
+      can't be `null`. Such comparisons are always either `true` or `false`, so
+      they serve no purpose.
+
+      #### Examples
+
+      The following code produces this diagnostic because `x` can never be
+      `null`, so the comparison always evaluates to `true`:
+
+      ```dart
+      void f(int x) {
+        if (x [!!= null!]) {
+          print(x);
+        }
+      }
+      ```
+
+      The following code produces this diagnostic because `x` can never be
+      `null`, so the comparison always evaluates to `false`:
+
+      ```dart
+      void f(int x) {
+        if (x [!== null!]) {
+          throw ArgumentError("x can't be null");
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If the other operand should be able to be `null`, then change the type of
+      the operand:
+
+      ```dart
+      void f(int? x) {
+        if (x != null) {
+          print(x);
+        }
+      }
+      ```
+
+      If the other operand really can't be `null`, then remove the condition:
+
+      ```dart
+      void f(int x) {
+        print(x);
+      }
+      ```
+  UNNECESSARY_NULL_COMPARISON_TRUE:
+    sharedName: UNNECESSARY_NULL_COMPARISON
+    problemMessage: "The operand can't be null, so the condition is always 'true'."
+    correctionMessage: Remove the condition.
+    hasPublishedDocs: true
+    comment: No parameters.
+  UNNECESSARY_QUESTION_MARK:
+    problemMessage: "The '?' is unnecessary because '{0}' is nullable without it."
+    hasPublishedDocs: true
+    comment: |-
+      Parameters:
+      0: the name of the type
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when either the type `dynamic` or the
+      type `Null` is followed by a question mark. Both of these types are
+      inherently nullable so the question mark doesn't change the semantics.
+
+      #### Example
+
+      The following code produces this diagnostic because the question mark
+      following `dynamic` isn't necessary:
+
+      ```dart
+      dynamic[!?!] x;
+      ```
+
+      #### Common fixes
+
+      Remove the unneeded question mark:
+
+      ```dart
+      dynamic x;
+      ```
+  UNNECESSARY_SET_LITERAL:
+    problemMessage: Braces unnecessarily wrap this expression in a set literal.
+    correctionMessage: Try removing the set literal around the expression.
+    hasPublishedDocs: false
+    comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a function that has a return
+      type of `void`, `Future<void>`, or `FutureOr<void>` uses an expression
+      function body (`=>`) and the returned value is a literal set containing a
+      single element.
+
+      Although the language allows it, returning a value from a `void` function
+      isn't useful because it can't be used at the call site. In this particular
+      case the return is often due to a misunderstanding about the syntax. The
+      braces aren't necessary and can be removed.
+
+      #### Example
+
+      The following code produces this diagnostic because the closure being
+      passed to `g` has a return type of `void`, but is returning a set:
+
+      ```dart
+      void f() {
+        g(() => [!{1}!]);
+      }
+
+      void g(void Function() p) {}
+      ```
+
+      #### Common fixes
+
+      Remove the braces from around the value:
+
+      ```dart
+      void f() {
+        g(() => 1);
+      }
+
+      void g(void Function() p) {}
+      ```
   UNNECESSARY_TYPE_CHECK_FALSE:
-    aliasFor: HintCode.UNNECESSARY_TYPE_CHECK_FALSE
-    comment: This is the new replacement for [HintCode.UNNECESSARY_TYPE_CHECK_FALSE].
+    sharedName: UNNECESSARY_TYPE_CHECK
+    problemMessage: "Unnecessary type check; the result is always 'false'."
+    correctionMessage: Try correcting the type check, or removing the type check.
+    hasPublishedDocs: true
+    comment: No parameters.
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when the value of a type check (using
+      either `is` or `is!`) is known at compile time.
+
+      #### Example
+
+      The following code produces this diagnostic because the test `a is Object?`
+      is always `true`:
+
+      ```dart
+      bool f<T>(T a) => [!a is Object?!];
+      ```
+
+      #### Common fixes
+
+      If the type check doesn't check what you intended to check, then change the
+      test:
+
+      ```dart
+      bool f<T>(T a) => a is Object;
+      ```
+
+      If the type check does check what you intended to check, then replace the
+      type check with its known value or completely remove it:
+
+      ```dart
+      bool f<T>(T a) => true;
+      ```
   UNNECESSARY_TYPE_CHECK_TRUE:
-    aliasFor: HintCode.UNNECESSARY_TYPE_CHECK_TRUE
-    comment: This is the new replacement for [HintCode.UNNECESSARY_TYPE_CHECK_TRUE].
+    sharedName: UNNECESSARY_TYPE_CHECK
+    problemMessage: "Unnecessary type check; the result is always 'true'."
+    correctionMessage: Try correcting the type check, or removing the type check.
+    hasPublishedDocs: true
+    comment: No parameters.
   UNNECESSARY_WILDCARD_PATTERN:
     problemMessage: Unnecessary wildcard pattern.
     correctionMessage: Try removing the wildcard pattern.
     comment: No parameters.
+  UNREACHABLE_SWITCH_CASE:
+    aliasFor: HintCode.UNREACHABLE_SWITCH_CASE
+    comment: This is the new replacement for [HintCode.UNREACHABLE_SWITCH_CASE].
   UNUSED_CATCH_CLAUSE:
     problemMessage: "The exception variable '{0}' isn't used, so the 'catch' clause can be removed."
     correctionMessage: Try removing the catch clause.
@@ -23635,6 +25093,144 @@
   UNUSED_IMPORT:
     aliasFor: HintCode.UNUSED_IMPORT
     comment: This is the new replacement for [HintCode.UNUSED_IMPORT].
+  UNUSED_LABEL:
+    problemMessage: "The label '{0}' isn't used."
+    correctionMessage: "Try removing the label, or using it in either a 'break' or 'continue' statement."
+    hasPublishedDocs: true
+    comment: |-
+      Parameters:
+      0: the label that isn't used
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a label that isn't used is
+      found.
+
+      #### Example
+
+      The following code produces this diagnostic because the label `loop` isn't
+      referenced anywhere in the method:
+
+      ```dart
+      void f(int limit) {
+        [!loop:!] for (int i = 0; i < limit; i++) {
+          print(i);
+        }
+      }
+      ```
+
+      #### Common fixes
+
+      If the label isn't needed, then remove it:
+
+      ```dart
+      void f(int limit) {
+        for (int i = 0; i < limit; i++) {
+          print(i);
+        }
+      }
+      ```
+
+      If the label is needed, then use it:
+
+      ```dart
+      void f(int limit) {
+        loop: for (int i = 0; i < limit; i++) {
+          print(i);
+          if (i != 0) {
+            break loop;
+          }
+        }
+      }
+      ```
+      TODO(brianwilkerson) Highlight the identifier without the colon.
   UNUSED_LOCAL_VARIABLE:
     aliasFor: HintCode.UNUSED_LOCAL_VARIABLE
     comment: This is the new replacement for [HintCode.UNUSED_LOCAL_VARIABLE].
+  UNUSED_RESULT:
+    problemMessage: "The value of '{0}' should be used."
+    correctionMessage: Try using the result by invoking a member, passing it to a function, or returning it from this function.
+    hasPublishedDocs: true
+    comment: |-
+      Parameters:
+      0: the name of the annotated method, property or function
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a function annotated with
+      `[useResult][meta-useResult]` is invoked, and the value returned by that
+      function isn't used. The value is considered to be used if a member of the
+      value is invoked, if the value is passed to another function, or if the
+      value is assigned to a variable or field.
+
+      #### Example
+
+      The following code produces this diagnostic because the invocation of
+      `c.a()` isn't used, even though the method `a` is annotated with
+      `[useResult][meta-useResult]`:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      class C {
+        @useResult
+        int a() => 0;
+
+        int b() => 0;
+      }
+
+      void f(C c) {
+        c.[!a!]();
+      }
+      ```
+
+      #### Common fixes
+
+      If you intended to invoke the annotated function, then use the value that
+      was returned:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      class C {
+        @useResult
+        int a() => 0;
+
+        int b() => 0;
+      }
+
+      void f(C c) {
+        print(c.a());
+      }
+      ```
+
+      If you intended to invoke a different function, then correct the name of
+      the function being invoked:
+
+      ```dart
+      import 'package:meta/meta.dart';
+
+      class C {
+        @useResult
+        int a() => 0;
+
+        int b() => 0;
+      }
+
+      void f(C c) {
+        c.b();
+      }
+      ```
+  UNUSED_RESULT_WITH_MESSAGE:
+    sharedName: UNUSED_RESULT
+    problemMessage: "'{0}' should be used. {1}."
+    correctionMessage: Try using the result by invoking a member, passing it to a function, or returning it from this function.
+    hasPublishedDocs: true
+    comment: |-
+      The result of invoking a method, property, or function annotated with
+      `@useResult` must be used (assigned, passed to a function as an argument,
+      or returned by a function).
+
+      Parameters:
+      0: the name of the annotated method, property or function
+      1: message details
diff --git a/analyzer/pubspec.yaml b/analyzer/pubspec.yaml
index f098d6f..23e1308 100644
--- a/analyzer/pubspec.yaml
+++ b/analyzer/pubspec.yaml
@@ -1,13 +1,14 @@
 name: analyzer
-version: 5.6.0
-description: This package provides a library that performs static analysis of Dart code.
+version: 5.10.0
+description: >-
+  This package provides a library that performs static analysis of Dart code.
 repository: https://github.com/dart-lang/sdk/tree/main/pkg/analyzer
 
 environment:
-  sdk: '>=2.17.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
-  _fe_analyzer_shared: ^54.0.0
+  _fe_analyzer_shared: ^58.0.0
   collection: ^1.17.0
   convert: ^3.0.0
   crypto: ^3.0.0
diff --git a/analyzer/tool/diagnostics/diagnostics.md b/analyzer/tool/diagnostics/diagnostics.md
index d6bcb3c..41b2dc2 100644
--- a/analyzer/tool/diagnostics/diagnostics.md
+++ b/analyzer/tool/diagnostics/diagnostics.md
@@ -285,6 +285,7 @@
 
 [ffi]: https://dart.dev/guides/libraries/c-interop
 [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_754
+[irrefutable pattern]: https://dart.dev/resources/glossary#irrefutable-pattern
 [meta-doNotStore]: https://pub.dev/documentation/meta/latest/meta/doNotStore-constant.html
 [meta-factory]: https://pub.dev/documentation/meta/latest/meta/factory-constant.html
 [meta-immutable]: https://pub.dev/documentation/meta/latest/meta/immutable-constant.html
@@ -297,6 +298,7 @@
 [meta-UseResult]: https://pub.dev/documentation/meta/latest/meta/UseResult-class.html
 [meta-visibleForOverriding]: https://pub.dev/documentation/meta/latest/meta/visibleForOverriding-constant.html
 [meta-visibleForTesting]: https://pub.dev/documentation/meta/latest/meta/visibleForTesting-constant.html
+[refutable pattern]: https://dart.dev/resources/glossary#refutable-pattern
 
 ### abi_specific_integer_invalid
 
@@ -321,7 +323,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class [!C!] extends AbiSpecificInteger {
+final class [!C!] extends AbiSpecificInteger {
 }
 {% endprettify %}
 
@@ -332,7 +334,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class [!C!] extends AbiSpecificInteger {
+final class [!C!] extends AbiSpecificInteger {
   C();
 }
 {% endprettify %}
@@ -344,7 +346,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class [!C!] extends AbiSpecificInteger {
+final class [!C!] extends AbiSpecificInteger {
   const C.zero();
   const C.one();
 }
@@ -357,7 +359,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class [!C!] extends AbiSpecificInteger {
+final class [!C!] extends AbiSpecificInteger {
   final int i;
 
   const C(this.i);
@@ -371,7 +373,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class [!C!]<T> extends AbiSpecificInteger { // type parameters
+final class [!C!]<T> extends AbiSpecificInteger { // type parameters
   const C();
 }
 {% endprettify %}
@@ -385,7 +387,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class C extends AbiSpecificInteger {
+final class C extends AbiSpecificInteger {
   const C();
 }
 {% endprettify %}
@@ -411,7 +413,7 @@
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
 @[!AbiSpecificIntegerMapping!]({Abi.linuxX64 : Uint16()})
-class C extends AbiSpecificInteger {
+final class C extends AbiSpecificInteger {
   const C();
 }
 {% endprettify %}
@@ -425,7 +427,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8(), Abi.linuxX64 : Uint16()})
-class C extends AbiSpecificInteger {
+final class C extends AbiSpecificInteger {
   const C();
 }
 {% endprettify %}
@@ -449,7 +451,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class [!C!] extends AbiSpecificInteger {
+final class [!C!] extends AbiSpecificInteger {
   const C();
 }
 {% endprettify %}
@@ -462,7 +464,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class C extends AbiSpecificInteger {
+final class C extends AbiSpecificInteger {
   const C();
 }
 {% endprettify %}
@@ -495,7 +497,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : [!Array<Uint8>(4)!]})
-class C extends AbiSpecificInteger {
+final class C extends AbiSpecificInteger {
   const C();
 }
 {% endprettify %}
@@ -508,7 +510,7 @@
 import 'dart:ffi';
 
 @AbiSpecificIntegerMapping({Abi.macosX64 : Int8()})
-class C extends AbiSpecificInteger {
+final class C extends AbiSpecificInteger {
   const C();
 }
 {% endprettify %}
@@ -562,6 +564,41 @@
 }
 {% endprettify %}
 
+### abstract_sealed_class
+
+_A class can't be declared both 'sealed' and 'abstract'._
+
+#### Description
+
+The analyzer produces this diagnostic when a class is declared using both
+the modifier `abstract` and the modifier `sealed`. Sealed classes are
+implicitly abstract, so explicitly using both modifiers is not allowed.
+
+#### Example
+
+The following code produces this diagnostic because the class `C` is
+declared using both `abstract` and `sealed`:
+
+{% prettify dart tag=pre+code %}
+abstract [!sealed!] class C {}
+{% endprettify %}
+
+#### Common fixes
+
+If the class should be abstract but not sealed, then remove the `sealed`
+modifier:
+
+{% prettify dart tag=pre+code %}
+abstract class C {}
+{% endprettify %}
+
+If the class should be both abstract and sealed, then remove the
+`abstract` modifier:
+
+{% prettify dart tag=pre+code %}
+sealed class C {}
+{% endprettify %}
+
 ### abstract_super_member_reference
 
 _The {0} '{1}' is always abstract in the supertype._
@@ -602,13 +639,13 @@
 
 #### Example
 
-Given a file named `a.dart` containing
+Given a file `a.dart` containing
 
 {% prettify dart tag=pre+code %}
 class C {}
 {% endprettify %}
 
-And a file named `b.dart` containing
+And a file `b.dart` containing
 
 {% prettify dart tag=pre+code %}
 class C {}
@@ -914,7 +951,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   [!@Double()!]
   external Pointer<Int8> p;
 }
@@ -927,7 +964,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   external Pointer<Int8> p;
 }
 {% endprettify %}
@@ -2264,6 +2301,42 @@
 int y = x as int;
 {% endprettify %}
 
+### class_used_as_mixin
+
+_The class '{0}' can't be used as a mixin because it's neither a mixin class nor
+a mixin._
+
+#### Description
+
+The analyzer produces this diagnostic when a class that is neither a
+`mixin class` nor a `mixin` is used in a `with` clause.
+
+#### Example
+
+The following code produces this diagnostic because the class `M` is being
+used as a mixin, but it isn't defined as a `mixin class`:
+
+{% prettify dart tag=pre+code %}
+class M {}
+class C with [!M!] {}
+{% endprettify %}
+
+#### Common fixes
+
+If the class can be a pure mixin, then change `class` to `mixin`:
+
+{% prettify dart tag=pre+code %}
+mixin M {}
+class C with M {}
+{% endprettify %}
+
+If the class needs to be both a class and a mixin, then add `mixin`:
+
+{% prettify dart tag=pre+code %}
+mixin class M {}
+class C with M {}
+{% endprettify %}
+
 ### collection_element_from_deferred_library
 
 _Constant values from a deferred library can't be used as keys in a 'const' map
@@ -2287,12 +2360,12 @@
 deferred import. Constants are evaluated at compile time, and values from
 deferred libraries aren't available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
-Given a file (`a.dart`) that defines the constant `zero`:
+Given a file `a.dart` that defines the constant `zero`:
 
 {% prettify dart tag=pre+code %}
 const zero = 0;
@@ -2353,7 +2426,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class [!S!] extends Struct implements Finalizable {
+final class [!S!] extends Struct implements Finalizable {
   external Pointer notEmpty;
 }
 {% endprettify %}
@@ -2365,7 +2438,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class S extends Struct {
+final class S extends Struct {
   external Pointer notEmpty;
 }
 {% endprettify %}
@@ -2621,6 +2694,95 @@
 }
 {% endprettify %}
 
+### constant_pattern_never_matches_value_type
+
+_The matched value type '{0}' can never be equal to this constant of type
+'{1}'._
+
+#### Description
+
+The analyzer produces this diagnostic when a constant pattern can never
+match the value it's being tested against because the type of the constant
+is known to never match the type of the value.
+
+#### Example
+
+The following code produces this diagnostic because the type of the
+constant pattern `(true)` is `bool`, and the type of the value being
+matched (`x`) is `int`, and a Boolean can never match an integer:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case [!true!]) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+If the type of the value is correct, then rewrite the pattern to be
+compatible:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case 3) {}
+}
+{% endprettify %}
+
+If the type of the constant is correct, then rewrite the value to be
+compatible:
+
+{% prettify dart tag=pre+code %}
+void f(bool x) {
+  if (x case true) {}
+}
+{% endprettify %}
+
+### constant_pattern_with_non_constant_expression
+
+_The expression of a constant pattern must be a valid constant._
+
+#### Description
+
+The analyzer produces this diagnostic when a constant pattern has an
+expression that isn't a valid constant.
+
+#### Example
+
+The following code produces this diagnostic because the constant pattern
+`i` isn't a constant:
+
+{% prettify dart tag=pre+code %}
+void f(int e, int i) {
+  switch (e) {
+    case [!i!]:
+      break;
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If the value that should be matched is known, then replace the expression
+with a constant:
+
+{% prettify dart tag=pre+code %}
+void f(int e, int i) {
+  switch (e) {
+    case 0:
+      break;
+  }
+}
+{% endprettify %}
+
+If the value that should be matched isn't known, then rewrite the code to
+not use a pattern:
+
+{% prettify dart tag=pre+code %}
+void f(int e, int i) {
+  if (e == i) {}
+}
+{% endprettify %}
+
 ### const_constructor_param_type_mismatch
 
 _A value of type '{0}' can't be assigned to a parameter of type '{1}' in a const
@@ -2842,8 +3004,8 @@
 Constants are evaluated at compile time, and classes from deferred
 libraries aren't available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
@@ -2926,8 +3088,8 @@
 a deferred import. Constants are evaluated at compile time, and values from
 deferred libraries aren't available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
@@ -3006,12 +3168,13 @@
 #### Description
 
 The analyzer produces this diagnostic when the class of object used as a
-key in a constant map literal implements the `==` operator. The
-implementation of constant maps uses the `==` operator, so any
-implementation other than the one inherited from `Object` requires
-executing arbitrary code at compile time, which isn't supported.
+key in a constant map literal implements either the `==` operator, the
+getter `hashCode`, or both. The implementation of constant maps uses both
+the `==` operator and the `hashCode` getter, so any implementation other
+than the ones inherited from `Object` requires executing arbitrary code at
+compile time, which isn't supported.
 
-#### Example
+#### Examples
 
 The following code produces this diagnostic because the constant map
 contains a key whose type is `C`, and the class `C` overrides the
@@ -3027,9 +3190,24 @@
 const map = {[!C()!] : 0};
 {% endprettify %}
 
+The following code produces this diagnostic because the constant map
+contains a key whose type is `C`, and the class `C` overrides the
+implementation of `hashCode`:
+
+{% prettify dart tag=pre+code %}
+class C {
+  const C();
+
+  int get hashCode => 3;
+}
+
+const map = {[!C()!] : 0};
+{% endprettify %}
+
 #### Common fixes
 
-If you can remove the implementation of `==` from the class, then do so:
+If you can remove the implementation of `==` and `hashCode` from the
+class, then do so:
 
 {% prettify dart tag=pre+code %}
 class C {
@@ -3039,8 +3217,8 @@
 const map = {C() : 0};
 {% endprettify %}
 
-If you can't remove the implementation of `==` from the class, then make
-the map be non-constant:
+If you can't remove the implementation of `==` and `hashCode` from the
+class, then make the map non-constant:
 
 {% prettify dart tag=pre+code %}
 class C {
@@ -3085,10 +3263,11 @@
 #### Description
 
 The analyzer produces this diagnostic when the class of object used as an
-element in a constant set literal implements the `==` operator. The
-implementation of constant sets uses the `==` operator, so any
-implementation other than the one inherited from `Object` requires
-executing arbitrary code at compile time, which isn't supported.
+element in a constant set literal implements either the `==` operator, the
+getter `hashCode`, or both. The implementation of constant sets uses both
+the `==` operator and the `hashCode` getter, so any implementation other
+than the ones inherited from `Object` requires executing arbitrary code at
+compile time, which isn't supported.
 
 #### Example
 
@@ -3106,9 +3285,24 @@
 const set = {[!C()!]};
 {% endprettify %}
 
+The following code produces this diagnostic because the constant set
+contains an element whose type is `C`, and the class `C` overrides the
+implementation of `hashCode`:
+
+{% prettify dart tag=pre+code %}
+class C {
+  const C();
+
+  int get hashCode => 3;
+}
+
+const map = {[!C()!]};
+{% endprettify %}
+
 #### Common fixes
 
-If you can remove the implementation of `==` from the class, then do so:
+If you can remove the implementation of `==` and `hashCode` from the
+class, then do so:
 
 {% prettify dart tag=pre+code %}
 class C {
@@ -3118,8 +3312,8 @@
 const set = {C()};
 {% endprettify %}
 
-If you can't remove the implementation of `==` from the class, then make
-the set be non-constant:
+If you can't remove the implementation of `==` and `hashCode` from the
+class, then make the set non-constant:
 
 {% prettify dart tag=pre+code %}
 class C {
@@ -3372,7 +3566,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   external int a;
 }
@@ -3391,7 +3585,7 @@
 import 'dart:ffi';
 import 'package:ffi/ffi.dart';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   external int a;
 }
@@ -3851,12 +4045,12 @@
 library. Extension methods are resolved at compile time, and extensions
 from deferred libraries aren't available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
-Given a file (`a.dart`) that defines a named extension:
+Given a file `a.dart` that defines a named extension:
 
 {% prettify dart tag=pre+code %}
 class C {}
@@ -4468,6 +4662,41 @@
 }
 {% endprettify %}
 
+### duplicate_field_name
+
+_The field name '{0}' is already used in this record._
+
+#### Description
+
+The analyzer produces this diagnostic when either a record literal or a
+record type annotation contains a field whose name is the same as a
+previously declared field in the same literal or type.
+
+#### Examples
+
+The following code produces this diagnostic because the record literal has
+two fields named `a`:
+
+{% prettify dart tag=pre+code %}
+var r = (a: 1, [!a!]: 2);
+{% endprettify %}
+
+The following code produces this diagnostic because the record type
+annotation has two fields named `a`, one a positional field and the other
+a named field:
+
+{% prettify dart tag=pre+code %}
+void f((int a, {int [!a!]}) r) {}
+{% endprettify %}
+
+#### Common fixes
+
+Rename one or both of the fields:
+
+{% prettify dart tag=pre+code %}
+var r = (a: 1, b: 2);
+{% endprettify %}
+
 ### duplicate_hidden_name
 
 _Duplicate hidden name._
@@ -4648,7 +4877,7 @@
 
 #### Example
 
-Given a file named `part.dart` containing
+Given a file `part.dart` containing
 
 {% prettify dart tag=pre+code %}
 part of lib;
@@ -4674,6 +4903,149 @@
 part 'part.dart';
 {% endprettify %}
 
+### duplicate_pattern_assignment_variable
+
+_The variable '{0}' is already assigned in this pattern._
+
+#### Description
+
+The analyzer produces this diagnostic when a single pattern variable is
+assigned a value more than once in the same pattern assignment.
+
+#### Example
+
+The following code produces this diagnostic because the variable `a` is
+assigned twice in the pattern `(a, a)`:
+
+{% prettify dart tag=pre+code %}
+int f((int, int) r) {
+  int a;
+  (a, [!a!]) = r;
+  return a;
+}
+{% endprettify %}
+
+#### Common fixes
+
+If you need to capture all of the values, then use a unique variable for
+each of the subpatterns being matched:
+
+{% prettify dart tag=pre+code %}
+int f((int, int) r) {
+  int a, b;
+  (a, b) = r;
+  return a + b;
+}
+{% endprettify %}
+
+If some of the values don't need to be captured, then use a wildcard
+pattern `_` to avoid having to bind the value to a variable:
+
+{% prettify dart tag=pre+code %}
+int f((int, int) r) {
+  int a;
+  (_, a) = r;
+  return a;
+}
+{% endprettify %}
+
+### duplicate_pattern_field
+
+_The field '{0}' is already matched in this pattern._
+
+#### Description
+
+The analyzer produces this diagnostic when a record pattern matches the
+same field more than once, or when an object pattern matches the same
+getter more than once.
+
+#### Examples
+
+The following code produces this diagnostic because the record field `a`
+is matched twice in the same record pattern:
+
+{% prettify dart tag=pre+code %}
+void f(({int a, int b}) r) {
+  switch (r) {
+    case (a: 1, [!a!]: 2):
+      return;
+  }
+}
+{% endprettify %}
+
+The following code produces this diagnostic because the getter `f` is
+matched twice in the same object pattern:
+
+{% prettify dart tag=pre+code %}
+void f(Object o) {
+  switch (0) {
+    case C(f: 1, [!f!]: 2):
+      return;
+  }
+}
+class C {
+  int? f;
+}
+{% endprettify %}
+
+#### Common fixes
+
+If the pattern should match for more than one value of the duplicated
+field, then use a logical-or pattern:
+
+{% prettify dart tag=pre+code %}
+void f(({int a, int b}) r) {
+  switch (r) {
+    case (a: 1, b: _) || (a: 2, b: _):
+      break;
+  }
+}
+{% endprettify %}
+
+If the pattern should match against multiple fields, then change the name
+of one of the fields:
+
+{% prettify dart tag=pre+code %}
+void f(({int a, int b}) r) {
+  switch (r) {
+    case (a: 1, b: 2):
+      return;
+  }
+}
+{% endprettify %}
+
+### duplicate_rest_element_in_pattern
+
+_At most one rest element is allowed in a list or map pattern._
+
+#### Description
+
+The analyzer produces this diagnostic when there's more than one rest
+pattern in either a list or map pattern. A rest pattern will capture any
+values unmatched by other subpatterns, making subsequent rest patterns
+unnecessary because there's nothing left to capture.
+
+#### Example
+
+The following code produces this diagnostic because there are two rest
+patterns in the list pattern:
+
+{% prettify dart tag=pre+code %}
+void f(List<int> x) {
+  if (x case [0, ..., [!...!]]) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+Remove all but one of the rest patterns:
+
+{% prettify dart tag=pre+code %}
+void f(List<int> x) {
+  if (x case [0, ...]) {}
+}
+{% endprettify %}
+
 ### duplicate_shown_name
 
 _Duplicate shown name._
@@ -4714,6 +5086,153 @@
 var x = min(2, min(0, 1));
 {% endprettify %}
 
+### duplicate_variable_pattern
+
+_The variable '{0}' is already defined in this pattern._
+
+#### Description
+
+The analyzer produces this diagnostic when a branch of a logical-and
+pattern declares a variable that is already declared in an earlier branch
+of the same pattern.
+
+#### Example
+
+The following code produces this diagnostic because the variable `a` is
+declared in both branches of the logical-and pattern:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  if (r case (var a, 0) && (0, var [!a!])) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If you need to capture the matched value in multiple branches, then change
+the names of the variables so that they are unique:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  if (r case (var a, 0) && (0, var b)) {
+    print(a + b);
+  }
+}
+{% endprettify %}
+
+If you only need to capture the matched value on one branch, then remove
+the variable pattern from all but one branch:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  if (r case (var a, 0) && (0, _)) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+### empty_record_literal_with_comma
+
+_A record literal without fields can't have a trailing comma._
+
+#### Description
+
+The analyzer produces this diagnostic when a record literal that has no
+fields has a trailing comma. Empty record literals can't contain a comma.
+
+#### Example
+
+The following code produces this diagnostic because the empty record
+literal has a trailing comma:
+
+{% prettify dart tag=pre+code %}
+var r = ([!,!]);
+{% endprettify %}
+
+#### Common fixes
+
+If the record is intended to be empty, then remove the comma:
+
+{% prettify dart tag=pre+code %}
+var r = ();
+{% endprettify %}
+
+If the record is intended to have one or more fields, then add the
+expressions used to compute the values of those fields:
+
+{% prettify dart tag=pre+code %}
+var r = (3, 4);
+{% endprettify %}
+
+### empty_record_type_named_fields_list
+
+_The list of named fields in a record type can't be empty._
+
+#### Description
+
+The analyzer produces this diagnostic when a record type has an empty list
+of named fields.
+
+#### Example
+
+The following code produces this diagnostic because the record type has an
+empty list of named fields:
+
+{% prettify dart tag=pre+code %}
+void f((int, int, {[!}!]) r) {}
+{% endprettify %}
+
+#### Common fixes
+
+If the record is intended to have named fields, then add the types and
+names of the fields:
+
+{% prettify dart tag=pre+code %}
+void f((int, int, {int z}) r) {}
+{% endprettify %}
+
+If the record isn't intended to have named fields, then remove the curly
+braces:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {}
+{% endprettify %}
+
+### empty_record_type_with_comma
+
+_A record type without fields can't have a trailing comma._
+
+#### Description
+
+The analyzer produces this diagnostic when a record type that has no
+fields has a trailing comma. Empty record types can't contain a comma.
+
+#### Example
+
+The following code produces this diagnostic because the empty record type
+has a trailing comma:
+
+{% prettify dart tag=pre+code %}
+void f(([!,!]) r) {}
+{% endprettify %}
+
+#### Common fixes
+
+If the record type is intended to be empty, then remove the comma:
+
+{% prettify dart tag=pre+code %}
+void f(() r) {}
+{% endprettify %}
+
+If the record type is intended to have one or more fields, then add the
+types of those fields:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {}
+{% endprettify %}
+
 ### empty_struct
 
 _The class '{0}' can't be empty because it's a subclass of '{1}'._
@@ -4734,7 +5253,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class [!C!] extends Struct {}
+final class [!C!] extends Struct {}
 {% endprettify %}
 
 #### Common fixes
@@ -4744,7 +5263,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   external int x;
 }
@@ -4756,7 +5275,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Opaque {}
+final class C extends Opaque {}
 {% endprettify %}
 
 If the class isn't intended to be a struct, then remove or change the
@@ -5110,6 +5629,77 @@
 of which entry to remove might affect the order in which the keys and
 values are returned by an iterator.
 
+### equal_keys_in_map_pattern
+
+_Two keys in a map pattern can't be equal._
+
+#### Description
+
+The analyzer produces this diagnostic when a map pattern contains more
+than one key with the same name. The same key can't be matched twice.
+
+#### Example
+
+The following code produces this diagnostic because the key `'a'` appears
+twice:
+
+{% prettify dart tag=pre+code %}
+void f(Map<String, int> x) {
+  if (x case {'a': 1, [!'a'!]: 2}) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+If you are trying to match two different keys, then change one of the keys
+in the pattern:
+
+{% prettify dart tag=pre+code %}
+void f(Map<String, int> x) {
+  if (x case {'a': 1, 'b': 2}) {}
+}
+{% endprettify %}
+
+If you are trying to match the same key, but allow any one of multiple
+patterns to match, the use a logical-or pattern:
+
+{% prettify dart tag=pre+code %}
+void f(Map<String, int> x) {
+  if (x case {'a': 1 || 2}) {}
+}
+{% endprettify %}
+
+### expected_one_list_pattern_type_arguments
+
+_List patterns require one type argument or none, but {0} found._
+
+#### Description
+
+The analyzer produces this diagnostic when a list pattern has more than
+one type argument. List patterns can have either zero type arguments or
+one type argument, but can't have more than one.
+
+#### Example
+
+The following code produces this diagnostic because the list pattern
+(`[0]`) has two type arguments:
+
+{% prettify dart tag=pre+code %}
+void f(Object x) {
+  if (x case [!<int, int>!][0]) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+Remove all but one of the type arguments:
+
+{% prettify dart tag=pre+code %}
+void f(Object x) {
+  if (x case <int>[0]) {}
+}
+{% endprettify %}
+
 ### expected_one_list_type_arguments
 
 _List literals require one type argument or none, but {0} found._
@@ -5162,6 +5752,38 @@
 var s = <int>{0, 1};
 {% endprettify %}
 
+### expected_two_map_pattern_type_arguments
+
+_Map patterns require two type arguments or none, but {0} found._
+
+#### Description
+
+The analyzer produces this diagnostic when a map pattern has either one
+type argument or more than two type arguments. Map patterns can have
+either two type arguments or zero type arguments, but can't have any other
+number.
+
+#### Example
+
+The following code produces this diagnostic because the map pattern
+(`<int>{}`) has one type argument:
+
+{% prettify dart tag=pre+code %}
+void f(Object x) {
+  if (x case [!<int>!]{}) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+Add or remove type arguments until there are two, or none:
+
+{% prettify dart tag=pre+code %}
+void f(Object x) {
+  if (x case <int, int>{}) {}
+}
+{% endprettify %}
+
 ### expected_two_map_type_arguments
 
 _Map literals require two type arguments or none, but {0} found._
@@ -5271,7 +5893,7 @@
 
 #### Example
 
-Given a file named `part.dart` containing
+Given a file `part.dart` containing
 
 {% prettify dart tag=pre+code %}
 part of lib;
@@ -5792,7 +6414,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   [!@Int16()!]
   external int x;
@@ -5805,7 +6427,7 @@
 
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   external int x;
 }
@@ -5908,7 +6530,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Array(4)
   [!@Array(8)!]
   external Array<Uint8> a0;
@@ -5922,7 +6544,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Array(8)
   external Array<Uint8> a0;
 }
@@ -6124,7 +6746,7 @@
 // @dart = 2.9
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   int f;
 
@@ -6140,7 +6762,7 @@
 // @dart = 2.9
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   int f;
 
@@ -6389,7 +7011,7 @@
 // @dart = 2.9
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   Pointer [!p!] = nullptr;
 }
 {% endprettify %}
@@ -6402,7 +7024,7 @@
 // @dart = 2.9
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   Pointer p;
 }
 {% endprettify %}
@@ -6426,7 +7048,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int16()
   int [!a!];
 }
@@ -6439,7 +7061,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int16()
   external int a;
 }
@@ -6852,7 +7474,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class [!S!]<T> extends Struct {
+final class [!S!]<T> extends Struct {
   external Pointer notEmpty;
 }
 {% endprettify %}
@@ -6864,7 +7486,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class S extends Struct {
+final class S extends Struct {
   external Pointer notEmpty;
 }
 {% endprettify %}
@@ -7236,7 +7858,7 @@
 in both the `extends` and `with` clauses for the class `B`:
 
 {% prettify dart tag=pre+code %}
-class A {}
+mixin class A {}
 
 class B extends A with [!A!] {}
 {% endprettify %}
@@ -7409,12 +8031,12 @@
 function is used to load the contents of the deferred library, and the
 implicit function hides the explicit declaration in the deferred library.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
-Given a file (`a.dart`) that defines a function named `loadLibrary`:
+Given a file `a.dart` that defines a function named `loadLibrary`:
 
 {% prettify dart tag=pre+code %}
 void loadLibrary(Library library) {}
@@ -7502,7 +8124,7 @@
 
 #### Example
 
-Given a file named `a.dart` that contains the following:
+Given a file `a.dart` that contains the following:
 
 {% prettify dart tag=pre+code %}
 // @dart = 2.9
@@ -7648,6 +8270,84 @@
 the code in the [part file][] to be consistent with the new language
 version.
 
+### inconsistent_pattern_variable_logical_or
+
+_The variable '{0}' has a different type and/or finality in this branch of the
+logical-or pattern._
+
+#### Description
+
+The analyzer produces this diagnostic when a pattern variable that is
+declared on all branches of a logical-or pattern doesn't have the same
+type on every branch. It is also produced when the variable has a
+different finality on different branches. A pattern variable declared on
+multiple branches of a logical-or pattern is required to have the same
+type and finality in each branch, so that the type and finality of the
+variable can be known in code that's guarded by the logical-or pattern.
+
+#### Examples
+
+The following code produces this diagnostic because the variable `a` is
+defined to be an `int` on one branch and a `double` on the other:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  if (x case (int a) || (double [!a!])) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+The following code produces this diagnostic because the variable `a` is
+`final` in the first branch and isn't `final` in the second branch:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  if (x case (final int a) || (int [!a!])) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If the finality of the variable is different, decide whether it should be
+`final` or not `final` and make the cases consistent:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  if (x case (int a) || (int a)) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+If the type of the variable is different and the type isn't critical to
+the condition being matched, then ensure that the variable has the same
+type on both branches:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  if (x case (num a) || (num a)) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+If the type of the variable is different and the type is critical to the
+condition being matched, then consider breaking the condition into
+multiple `if` statements or `case` clauses:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  if (x case int a) {
+    print(a);
+  } else if (x case double a) {
+    print(a);
+  }
+}
+{% endprettify %}
+
 ### initializer_for_non_existent_field
 
 _'{0}' isn't a field in the enclosing class._
@@ -8209,8 +8909,8 @@
 of an annotation. Annotations are evaluated at compile time, and values
 from deferred libraries aren't available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
@@ -8260,8 +8960,8 @@
 are evaluated at compile time, and constants from deferred libraries aren't
 available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
@@ -8470,7 +9170,7 @@
 
 #### Example
 
-Given a file named `a.dart` in the `src` directory that contains:
+Given a file `a.dart` in the `src` directory that contains:
 
 {% prettify dart tag=pre+code %}
 import 'package:meta/meta.dart';
@@ -8510,7 +9210,7 @@
 
 #### Example
 
-Given a file named `a.dart` in the `src` directory that contains the
+Given a file `a.dart` in the `src` directory that contains the
 following:
 
 {% prettify dart tag=pre+code %}
@@ -8747,6 +9447,80 @@
 }
 {% endprettify %}
 
+### invalid_field_name
+
+_Record field names can't be a dollar sign followed by an integer when the
+integer is the index of a positional field._
+
+_Record field names can't be private._
+
+_Record field names can't be the same as a member from 'Object'._
+
+#### Description
+
+The analyzer produces this diagnostic when either a record literal or a
+record type annotation has a field whose name is invalid. The name is
+invalid if it is:
+- private (starts with `_`)
+- the same as one of the members defined on `Object`
+- the same as the name of a positional field (an exception is made if the
+  field is a positional field with the specified name)
+
+#### Examples
+
+The following code produces this diagnostic because the record literal has
+a field named `toString`, which is a method defined on `Object`:
+
+{% prettify dart tag=pre+code %}
+var r = (a: 1, [!toString!]: 4);
+{% endprettify %}
+
+The following code produces this diagnostic because the record type
+annotation has a field named `hashCode`, which is a getter defined on
+`Object`:
+
+{% prettify dart tag=pre+code %}
+void f(({int a, int [!hashCode!]}) r) {}
+{% endprettify %}
+
+The following code produces this diagnostic because the record literal has
+a private field named `_a`:
+
+{% prettify dart tag=pre+code %}
+var r = ([!_a!]: 1, b: 2);
+{% endprettify %}
+
+The following code produces this diagnostic because the record type
+annotation has a private field named `_a`:
+
+{% prettify dart tag=pre+code %}
+void f(({int [!_a!], int b}) r) {}
+{% endprettify %}
+
+The following code produces this diagnostic because the record literal has
+a field named `$1`, which is also the name of a different positional
+parameter:
+
+{% prettify dart tag=pre+code %}
+var r = (2, [!$1!]: 1);
+{% endprettify %}
+
+The following code produces this diagnostic because the record type
+annotation has a field named `$1`, which is also the name of a different
+positional parameter:
+
+{% prettify dart tag=pre+code %}
+void f((int, String, {int [!$1!]}) r) {}
+{% endprettify %}
+
+#### Common fixes
+
+Rename the field:
+
+{% prettify dart tag=pre+code %}
+var r = (a: 1, d: 4);
+{% endprettify %}
+
 ### invalid_field_type_in_struct
 
 _Fields in struct classes can't have the type '{0}'. They can only be declared
@@ -8769,7 +9543,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   external [!String!] s;
 
   @Int32()
@@ -8785,7 +9559,7 @@
 import 'dart:ffi';
 import 'package:ffi/ffi.dart';
 
-class C extends Struct {
+final class C extends Struct {
   external Pointer<Utf8> s;
 
   @Int32()
@@ -9436,6 +10210,163 @@
 }
 {% endprettify %}
 
+### invalid_pattern_variable_in_shared_case_scope
+
+_The variable '{0}' doesn't have the same type and/or finality in all cases that
+share this body._
+
+_The variable '{0}' is available in some, but not all cases that share this
+body._
+
+_The variable '{0}' is not available because there is a label or 'default'
+case._
+
+#### Description
+
+The analyzer produces this diagnostic when multiple case clauses in a
+switch statement share a body, and at least one of them declares a
+variable that is referenced in the shared statements, but the variable is
+either not declared in all of the case clauses or it is declared in
+inconsistent ways.
+
+If the variable isn't declared in all of the case clauses, then it won't
+have a value if one of the clauses that doesn't declare the variable is
+the one that matches and executes the body. This includes the situation
+where one of the case clauses is the `default` clause.
+
+If the variable is declared in inconsistent ways, either being `final` in
+some cases and not `final` in others or having a different type in
+different cases, then the semantics of what the type or finality of the
+variable should be are not defined.
+
+#### Examples
+
+The following code produces this diagnostic because the variable `a` is
+only declared in one of the case clauses, and won't have a value if the
+second clause is the one that matched `x`:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    case int a when a > 0:
+    case 0:
+      [!a!];
+  }
+}
+{% endprettify %}
+
+The following code produces this diagnostic because the variable `a` isn't
+declared in the `default` clause, and won't have a value if the body is
+executed because none of the other clauses matched `x`:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    case int a when a > 0:
+    default:
+      [!a!];
+  }
+}
+{% endprettify %}
+
+The following code produces this diagnostic because the variable `a` won't
+have a value if the body is executed because a different group of cases
+caused control to continue at the label:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    someLabel:
+    case int a when a > 0:
+      [!a!];
+    case int b when b < 0:
+      continue someLabel;
+  }
+}
+{% endprettify %}
+
+The following code produces this diagnostic because the variable `a`,
+while being assigned in all of the case clauses, doesn't have then same
+type associated with it in every clause:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    case int a when a < 0:
+    case num a when a > 0:
+      [!a!];
+  }
+}
+{% endprettify %}
+
+The following code produces this diagnostic because the variable `a` is
+`final` in the first case clause and isn't `final` in the second case
+clause:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    case final int a when a < 0:
+    case int a when a > 0:
+      [!a!];
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If the variable isn't declared in all of the cases, and you need to
+reference it in the statements, then declare it in the other cases:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    case int a when a > 0:
+    case int a when a == 0:
+      a;
+  }
+}
+{% endprettify %}
+
+If the variable isn't declared in all of the cases, and you don't need to
+reference it in the statements, then remove the references to it and
+remove the declarations from the other cases:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  switch (x) {
+    case > 0:
+    case 0:
+  }
+}
+{% endprettify %}
+
+If the type of the variable is different, decide the type the variable
+should have and make the cases consistent:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    case num a when a < 0:
+    case num a when a > 0:
+      a;
+  }
+}
+{% endprettify %}
+
+If the finality of the variable is different, decide whether it should be
+`final` or not `final` and make the cases consistent:
+
+{% prettify dart tag=pre+code %}
+void f(Object? x) {
+  switch (x) {
+    case final int a when a < 0:
+    case final int a when a > 0:
+      a;
+  }
+}
+{% endprettify %}
+
 ### invalid_reference_to_generative_enum_constructor
 
 _Generative enum constructors can only be used as targets of redirection._
@@ -9877,6 +10808,83 @@
 }
 {% endprettify %}
 
+### invalid_use_of_type_outside_library
+
+_The class '{0}' can't be extended outside of its library because it's a final
+class._
+
+_The class '{0}' can't be extended outside of its library because it's an
+interface class._
+
+_The class '{0}' can't be extended, implemented, or mixed in outside of its
+library because it's a sealed class._
+
+_The class '{0}' can't be implemented outside of its library because it's a base
+class._
+
+_The class '{0}' can't be implemented outside of its library because it's a
+final class._
+
+_The mixin '{0}' can't be implemented outside of its library because it's a base
+mixin._
+
+_The mixin '{0}' can't be implemented outside of its library because it's a
+final mixin._
+
+_The mixin '{0}' can't be mixed in outside of its library because it's a sealed
+mixin._
+
+_The mixin '{0}' can't be mixed-in outside of its library because it's a final
+mixin._
+
+_The mixin '{0}' can't be mixed-in outside of its library because it's an
+interface mixin._
+
+#### Description
+
+The analyzer produces this diagnostic when an `extends`, `implements`,
+`with`, or `on` clause uses a class or mixin in a way that isn't allowed
+given the modifiers on that class or mixin's declaration.
+
+The message specifies how the declaration is being used and why it isn't
+allowed.
+
+#### Example
+
+Given a file `a.dart` that defines a base class `A`:
+
+{% prettify dart tag=pre+code %}
+base class A {}
+{% endprettify %}
+
+The following code produces this diagnostic because the class `B`
+implements the class `A`, but the `base` modifier prevents `A` from being
+implemented outside of the library where it's defined:
+
+{% prettify dart tag=pre+code %}
+import 'a.dart';
+
+final class B implements [!A!] {}
+{% endprettify %}
+
+#### Common fixes
+
+Use of this type is restricted outside of its declaring library. If a
+different, unrestricted type is available that can provide similar
+functionality, then replace the type:
+
+{% prettify dart tag=pre+code %}
+class B implements C {}
+class C {}
+{% endprettify %}
+
+If there isn't a different type that would be appropriate, then remove the
+type, and possibly the whole clause:
+
+{% prettify dart tag=pre+code %}
+class B {}
+{% endprettify %}
+
 ### invalid_use_of_visible_for_overriding_member
 
 _The member '{0}' can only be used for overriding._
@@ -9890,7 +10898,7 @@
 
 #### Example
 
-Given a file named `a.dart` containing the following declaration:
+Given a file `a.dart` containing the following declaration:
 
 {% prettify dart tag=pre+code %}
 import 'package:meta/meta.dart';
@@ -9931,7 +10939,7 @@
 
 #### Example
 
-Given a file named `c.dart` that contains the following:
+Given a file `c.dart` that contains the following:
 
 {% prettify dart tag=pre+code %}
 import 'package:meta/meta.dart';
@@ -10793,7 +11801,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   [!@Double()!]
   external int x;
 }
@@ -10806,7 +11814,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   external int x;
 }
@@ -10817,7 +11825,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Double()
   external double x;
 }
@@ -10846,7 +11854,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   external [!int!] x;
 }
 {% endprettify %}
@@ -10858,7 +11866,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int64()
   external int x;
 }
@@ -11061,7 +12069,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   external var [!str!];
 
   @Int32()
@@ -11077,7 +12085,7 @@
 import 'dart:ffi';
 import 'package:ffi/ffi.dart';
 
-class C extends Struct {
+final class C extends Struct {
   external Pointer<Utf8> str;
 
   @Int32()
@@ -11114,6 +12122,74 @@
   meta: ^1.0.2
 ```
 
+### missing_object_pattern_getter_name
+
+_The getter name is not specified explicitly, and the pattern is not a
+variable._
+
+#### Description
+
+The analyzer produces this diagnostic when, within an object pattern, the
+specification of a property and the pattern used to match the property's
+value doesn't have either:
+- a getter name before the colon
+- a variable pattern from which the getter name can be inferred
+
+#### Example
+
+The following code produces this diagnostic because there is no getter
+name before the colon and no variable pattern after the colon in the
+object pattern (`C(:0)`):
+
+{% prettify dart tag=pre+code %}
+abstract class C {
+  int get f;
+}
+
+void f(C c) {
+  switch (c) {
+    case C([!:0!]):
+      break;
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If you need to use the actual value of the property within the pattern's
+scope, then add a variable pattern where the name of the variable is the
+same as the name of the property being matched:
+
+{% prettify dart tag=pre+code %}
+abstract class C {
+  int get f;
+}
+
+void f(C c) {
+  switch (c) {
+    case C(:var f) when f == 0:
+      print(f);
+  }
+}
+{% endprettify %}
+
+If you don't need to use the actual value of the property within the
+pattern's scope, then add the name of the property being matched before
+the colon:
+
+{% prettify dart tag=pre+code %}
+abstract class C {
+  int get f;
+}
+
+void f(C c) {
+  switch (c) {
+    case C(f: 0):
+      break;
+  }
+}
+{% endprettify %}
+
 ### missing_override_of_must_be_overridden
 
 _Missing concrete implementation of '{0}'._
@@ -11296,7 +12372,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   external [!Array<Uint8>!] a0;
 }
 {% endprettify %}
@@ -11308,12 +12384,76 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Array(8)
   external Array<Uint8> a0;
 }
 {% endprettify %}
 
+### missing_variable_pattern
+
+_Variable pattern '{0}' is missing in this branch of the logical-or pattern._
+
+#### Description
+
+The analyzer produces this diagnostic when one branch of a logical-or
+pattern doesn't declare a variable that is declared on the other branch of
+the same pattern.
+
+#### Example
+
+The following code produces this diagnostic because the right-hand side of
+the logical-or pattern doesn't declare the variable `a`:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  if (r case (var a, 0) || [!(0, _)!]) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If the variable needs to be referenced in the controlled statements, then
+add a declaration of the variable to every branch of the logical-or
+pattern:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  if (r case (var a, 0) || (0, var a)) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+If the variable doesn't need to be referenced in the controlled
+statements, then remove the declaration of the variable from every branch
+of the logical-or pattern:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  if (r case (_, 0) || (0, _)) {
+    print('found a zero');
+  }
+}
+{% endprettify %}
+
+If the variable needs to be referenced if one branch of the pattern
+matches but not when the other matches, then break the pattern into two
+pieces:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  switch (r) {
+    case (var a, 0):
+      print(a);
+    case (0, _):
+      print('found a zero');
+  }
+}
+{% endprettify %}
+
 ### mixin_application_concrete_super_invoked_member_type
 
 _The super-invoked member '{0}' has the type '{1}', and the concrete member in
@@ -11543,6 +12683,7 @@
 defines a constructor, is being used as a mixin:
 
 {% prettify dart tag=pre+code %}
+//@dart=2.19
 class A {
   A();
 }
@@ -11565,6 +12706,7 @@
 then do so:
 
 {% prettify dart tag=pre+code %}
+//@dart=2.19
 class A {
 }
 
@@ -11598,6 +12740,7 @@
 extends `A`, is being used as a mixin by `C`:
 
 {% prettify dart tag=pre+code %}
+//@dart=2.19
 class A {}
 
 class B extends A {}
@@ -11611,6 +12754,7 @@
 change it:
 
 {% prettify dart tag=pre+code %}
+//@dart=2.19
 class A {}
 
 class B {}
@@ -12492,12 +13636,12 @@
 constants referenced in case clauses need to be available at compile time,
 and constants from deferred libraries aren't available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
-Given a file (`a.dart`) that defines the constant `zero`:
+Given a file `a.dart` that defines the constant `zero`:
 
 {% prettify dart tag=pre+code %}
 const zero = 0;
@@ -12616,12 +13760,12 @@
 Default values need to be available at compile time, and constants from
 deferred libraries aren't available at compile time.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
-Given a file (`a.dart`) that defines the constant `zero`:
+Given a file `a.dart` that defines the constant `zero`:
 
 {% prettify dart tag=pre+code %}
 const zero = 0;
@@ -12777,6 +13921,44 @@
 var m = {a: 0};
 {% endprettify %}
 
+### non_constant_map_pattern_key
+
+_Key expressions in map patterns must be constants._
+
+#### Description
+
+The analyzer produces this diagnostic when a key in a map pattern isn't a
+constant expression.
+
+#### Example
+
+The following code produces this diagnostic because the key `A()` isn't a
+constant:
+
+{% prettify dart tag=pre+code %}
+void f(Object x) {
+  if (x case {[!A()!]: 0}) {}
+}
+
+class A {
+  const A();
+}
+{% endprettify %}
+
+#### Common fixes
+
+Use a constant for the key:
+
+{% prettify dart tag=pre+code %}
+void f(Object x) {
+  if (x case {const A(): 0}) {}
+}
+
+class A {
+  const A();
+}
+{% endprettify %}
+
 ### non_constant_map_value
 
 _The values in a const map literal must be constant._
@@ -12812,6 +13994,40 @@
 var m = {0: a};
 {% endprettify %}
 
+### non_constant_relational_pattern_expression
+
+_The relational pattern expression must be a constant._
+
+#### Description
+
+The analyzer produces this diagnostic when the value in a relational
+pattern expression isn't a constant expression.
+
+#### Example
+
+The following code produces this diagnostic because the operand of the `>`
+operator, `a`, isn't a constant:
+
+{% prettify dart tag=pre+code %}
+final a = 0;
+
+void f(int x) {
+  if (x case > [!a!]) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+Replace the value with a constant expression:
+
+{% prettify dart tag=pre+code %}
+const a = 0;
+
+void f(int x) {
+  if (x case > a) {}
+}
+{% endprettify %}
+
 ### non_constant_set_element
 
 _The values in a const set literal must be constants._
@@ -12972,6 +14188,71 @@
 }
 {% endprettify %}
 
+### non_exhaustive_switch
+
+_The type '{0}' is not exhaustively matched by the switch cases._
+
+#### Description
+
+The analyzer produces this diagnostic when a `switch` statement switching
+over an exhaustive type is missing a case for one or more of the possible
+values that could flow through it.
+
+#### Example
+
+The following code produces this diagnostic because the switch statement
+doesn't have a case for the value `E.three`, and `E` is an exhaustive
+type:
+
+{% prettify dart tag=pre+code %}
+enum E { one, two, three }
+
+void f(E e) {
+  [!switch!] (e) {
+    case E.one:
+    case E.two:
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+Add a case for each of the constants that aren't currently being matched:
+
+{% prettify dart tag=pre+code %}
+enum E { one, two, three }
+
+void f(E e) {
+  switch (e) {
+    case E.one:
+    case E.two:
+      break;
+    case E.three:
+  }
+}
+{% endprettify %}
+
+If the missing values don't need to be matched, then add a `default`
+clause (or a wildcard pattern in a `switch` expression):
+
+{% prettify dart tag=pre+code %}
+enum E { one, two, three }
+
+void f(E e) {
+  switch (e) {
+    case E.one:
+    case E.two:
+      break;
+    default:
+  }
+}
+{% endprettify %}
+
+But be aware that adding a `default` clause or wildcard pattern will cause
+any future values of the exhaustive type to also be handled, so you will
+have lost the ability for the compiler to warn you if the `switch` needs
+to be updated.
+
 ### non_final_field_in_enum
 
 _Enums can only declare final fields._
@@ -13198,7 +14479,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class MyStruct extends Struct {
+final class MyStruct extends Struct {
   @Array([!-8!])
   external Array<Uint8> a0;
 }
@@ -13211,7 +14492,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class MyStruct extends Struct {
+final class MyStruct extends Struct {
   @Array(8)
   external Array<Uint8> a0;
 }
@@ -13239,7 +14520,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Array(8)
   external Array<[!Void!]> a0;
 }
@@ -13252,7 +14533,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Array(8)
   external Array<Uint8> a0;
 }
@@ -14410,7 +15691,7 @@
 
 @Packed(1)
 [!@Packed(1)!]
-class C extends Struct {
+final class C extends Struct {
   external Pointer<Uint8> notEmpty;
 }
 {% endprettify %}
@@ -14423,7 +15704,7 @@
 import 'dart:ffi';
 
 @Packed(1)
-class C extends Struct {
+final class C extends Struct {
   external Pointer<Uint8> notEmpty;
 }
 {% endprettify %}
@@ -14448,7 +15729,7 @@
 import 'dart:ffi';
 
 @Packed([!3!])
-class C extends Struct {
+final class C extends Struct {
   external Pointer<Uint8> notEmpty;
 }
 {% endprettify %}
@@ -14461,7 +15742,7 @@
 import 'dart:ffi';
 
 @Packed(4)
-class C extends Struct {
+final class C extends Struct {
   external Pointer<Uint8> notEmpty;
 }
 {% endprettify %}
@@ -14478,7 +15759,7 @@
 
 #### Example
 
-Given a file named `part.dart` containing
+Given a file `part.dart` containing
 
 {% prettify dart tag=pre+code %}
 part of 'library.dart';
@@ -14512,7 +15793,7 @@
 
 #### Example
 
-Given a file (`a.dart`) containing:
+Given a file `a.dart` containing:
 
 {% prettify dart tag=pre+code %}
 class A {}
@@ -14645,7 +15926,7 @@
 
 #### Example
 
-Assuming that the directory `local_package` doesn't contain a file named
+Assuming that the directory `local_package` doesn't contain a file
 `pubspec.yaml`, the following code produces this diagnostic because it's
 listed as the path of a package:
 
@@ -14667,6 +15948,224 @@
 
 If the path is wrong, then replace it with the correct path.
 
+### pattern_assignment_not_local_variable
+
+_Only local variables can be assigned in pattern assignments._
+
+#### Description
+
+The analyzer produces this diagnostic when a pattern assignment assigns a
+value to anything other than a local variable. Patterns can't assign to
+fields or top-level variables.
+
+#### Example
+
+If the code is cleaner when destructuring with a pattern, then rewrite the
+code to assign the value to a local variable in a pattern declaration,
+assigning the non-local variable separately:
+
+{% prettify dart tag=pre+code %}
+class C {
+  var x = 0;
+
+  void f((int, int) r) {
+    ([!x!], _) = r;
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If the code is cleaner when using a pattern assignment, then rewrite the
+code to assign the value to a local variable, assigning the non-local
+variable separately:
+
+{% prettify dart tag=pre+code %}
+class C {
+  var x = 0;
+
+  void f((int, int) r) {
+    var (a, _) = r;
+    x = a;
+  }
+}
+{% endprettify %}
+
+If the code is cleaner without using a pattern assignment, then rewrite
+the code to not use a pattern assignment:
+
+{% prettify dart tag=pre+code %}
+class C {
+  var x = 0;
+
+  void f((int, int) r) {
+    x = r.$1;
+  }
+}
+{% endprettify %}
+
+### pattern_constant_from_deferred_library
+
+_Constant values from a deferred library can't be used in patterns._
+
+#### Description
+
+The analyzer produces this diagnostic when a pattern contains a value
+declared in a different library, and that library is imported using a
+deferred import. Constants are evaluated at compile time, but values from
+deferred libraries aren't available at compile time.
+
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
+
+#### Example
+
+Given a file `a.dart` that defines the constant `zero`:
+
+{% prettify dart tag=pre+code %}
+const zero = 0;
+{% endprettify %}
+
+The following code produces this diagnostic because the constant pattern
+`a.zero` is imported using a deferred import:
+
+{% prettify dart tag=pre+code %}
+import 'a.dart' deferred as a;
+
+void f(int x) {
+  switch (x) {
+    case a.[!zero!]:
+      // ...
+      break;
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If you need to reference the constant from the imported library, then
+remove the `deferred` keyword:
+
+{% prettify dart tag=pre+code %}
+import 'a.dart' as a;
+
+void f(int x) {
+  switch (x) {
+    case a.zero:
+      // ...
+      break;
+  }
+}
+{% endprettify %}
+
+If you need to reference the constant from the imported library and also
+need the imported library to be deferred, then rewrite the switch
+statement as a sequence of `if` statements:
+
+{% prettify dart tag=pre+code %}
+import 'a.dart' deferred as a;
+
+void f(int x) {
+  if (x == a.zero) {
+    // ...
+  }
+}
+{% endprettify %}
+
+If you don't need to reference the constant, then replace the case
+expression:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  switch (x) {
+    case 0:
+      // ...
+      break;
+  }
+}
+{% endprettify %}
+
+### pattern_type_mismatch_in_irrefutable_context
+
+_The matched value of type '{0}' isn't assignable to the required type '{1}'._
+
+#### Description
+
+The analyzer produces this diagnostic when the type of the value on the
+right-hand side of a pattern assignment or pattern declaration doesn't
+match the type required by the pattern being used to match it.
+
+#### Example
+
+The following code produces this diagnostic because `x` might not be a
+`String` and hence might not match the object pattern:
+
+{% prettify dart tag=pre+code %}
+void f(Object x) {
+  var [!String(length: a)!] = x;
+  print(a);
+}
+{% endprettify %}
+
+#### Common fixes
+
+Change the code so that the type of the expression on the right-hand side
+matches the type required by the pattern:
+
+{% prettify dart tag=pre+code %}
+void f(String x) {
+  var String(length: a) = x;
+  print(a);
+}
+{% endprettify %}
+
+### pattern_variable_assignment_inside_guard
+
+_Pattern variables can't be assigned inside the guard of the enclosing guarded
+pattern._
+
+#### Description
+
+The analyzer produces this diagnostic when a pattern variable is assigned
+a value inside a guard (`when`) clause.
+
+#### Example
+
+The following code produces this diagnostic because the variable `a` is
+assigned a value inside the guard clause:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case var a when ([!a!] = 1) > 0) {
+    print(a);
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+If there's a value you need to capture, then assign it to a different
+variable:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  var b;
+  if (x case var a when (b = 1) > 0) {
+    print(a + b);
+  }
+}
+{% endprettify %}
+
+If there isn't a value you need to capture, then remove the assignment:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case var a when 1 > 0) {
+    print(a);
+  }
+}
+{% endprettify %}
+
 ### positional_super_formal_parameter_with_positional_argument
 
 _Positional super parameters can't be used when the super constructor invocation
@@ -14874,7 +16373,7 @@
 
 #### Example
 
-Given a file named `a.dart` containing the following code:
+Given a file `a.dart` containing the following code:
 
 {% prettify dart tag=pre+code %}
 mixin A {
@@ -14951,7 +16450,7 @@
 
 #### Example
 
-Given a file named `a.dart` that contains the following:
+Given a file `a.dart` that contains the following:
 
 {% prettify dart tag=pre+code %}
 class A {
@@ -15024,6 +16523,66 @@
 }
 {% endprettify %}
 
+### record_literal_one_positional_no_trailing_comma
+
+_A record literal with exactly one positional field requires a trailing comma._
+
+#### Description
+
+The analyzer produces this diagnostic when a record literal with a single
+positional field doesn't have a trailing comma after the field.
+
+In some locations a record literal with a single positional field could
+also be a parenthesized expression. A trailing comma is required to
+disambiguate these two valid interpretations.
+
+#### Example
+
+The following code produces this diagnostic because the record literal has
+one positional field but doesn't have a trailing comma:
+
+{% prettify dart tag=pre+code %}
+var r = const (1[!)!];
+{% endprettify %}
+
+#### Common fixes
+
+Add a trailing comma:
+
+{% prettify dart tag=pre+code %}
+var r = const (1,);
+{% endprettify %}
+
+### record_type_one_positional_no_trailing_comma
+
+_A record type with exactly one positional field requires a trailing comma._
+
+#### Description
+
+The analyzer produces this diagnostic when a record type annotation with a
+single positional field doesn't have a trailing comma after the field.
+
+In some locations a record type with a single positional field could also
+be a parenthesized expression. A trailing comma is required to
+disambiguate these two valid interpretations.
+
+#### Example
+
+The following code produces this diagnostic because the record type has
+one positional field, but doesn't have a trailing comma:
+
+{% prettify dart tag=pre+code %}
+void f((int[!)!] r) {}
+{% endprettify %}
+
+#### Common fixes
+
+Add a trailing comma:
+
+{% prettify dart tag=pre+code %}
+void f((int,) r) {}
+{% endprettify %}
+
 ### recursive_compile_time_constant
 
 _The compile-time constant expression depends on itself._
@@ -15652,6 +17211,184 @@
 }
 {% endprettify %}
 
+### refutable_pattern_in_irrefutable_context
+
+_Refutable patterns can't be used in an irrefutable context._
+
+#### Description
+
+The analyzer produces this diagnostic when a [refutable pattern][] is used
+in a context where only an [irrefutable pattern][] is allowed.
+
+The refutable patterns that are disallowed are:
+- logical-or
+- relational
+- null-check
+- constant
+
+The contexts that are checked are:
+- pattern-based variable declarations
+- pattern-based for loops
+- assignments with a pattern on the left-hand side
+
+#### Example
+
+The following code produces this diagnostic because the null-check
+pattern, which is a refutable pattern, is in a pattern-based variable
+declaration, which doesn't allow refutable patterns:
+
+{% prettify dart tag=pre+code %}
+void f(int? x) {
+  var ([!_?!]) = x;
+}
+{% endprettify %}
+
+#### Common fixes
+
+Rewrite the code to not use a refutable pattern in an irrefutable context.
+
+### relational_pattern_operator_return_type_not_assignable_to_bool
+
+_The return type of operators used in relational patterns must be assignable to
+'bool'._
+
+#### Description
+
+The analyzer produces this diagnostic when a relational pattern references
+an operator that doesn't produce a value of type `bool`.
+
+#### Example
+
+The following code produces this diagnostic because the operator `>`, used
+in the relational pattern `> c2`, returns a value of type `int` rather
+than a `bool`:
+
+{% prettify dart tag=pre+code %}
+class C {
+  const C();
+
+  int operator >(C c) => 3;
+
+  bool operator <(C c) => false;
+}
+
+const C c2 = C();
+
+void f(C c1) {
+  if (c1 case [!>!] c2) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+If there's a different operator that should be used, then change the
+operator:
+
+{% prettify dart tag=pre+code %}
+class C {
+  const C();
+
+  int operator >(C c) => 3;
+
+  bool operator <(C c) => false;
+}
+
+const C c2 = C();
+
+void f(C c1) {
+  if (c1 case < c2) {}
+}
+{% endprettify %}
+
+If the operator is expected to return `bool`, then update the declaration
+of the operator:
+
+{% prettify dart tag=pre+code %}
+class C {
+  const C();
+
+  bool operator >(C c) => true;
+
+  bool operator <(C c) => false;
+}
+
+const C c2 = C();
+
+void f(C c1) {
+  if (c1 case > c2) {}
+}
+{% endprettify %}
+
+### rest_element_not_last_in_map_pattern
+
+_A rest element in a map pattern must be the last element._
+
+#### Description
+
+The analyzer produces this diagnostic when a map pattern contains entries
+after a rest pattern. The rest pattern will match map entries whose keys
+aren't matched by any of the entries in the pattern. To make those
+semantics clear, the language requires that the rest pattern be the last
+entry in the list.
+
+#### Example
+
+The following code produces this diagnostic because the rest pattern is
+followed by another map pattern entry (`0: _`):
+
+{% prettify dart tag=pre+code %}
+void f(Map<int, String> x) {
+  if (x case {[!...!], 0: _}) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+Move the rest pattern to the end of the map pattern:
+
+{% prettify dart tag=pre+code %}
+void f(Map<int, String> x) {
+  if (x case {0: _, ...}) {}
+}
+{% endprettify %}
+
+### rest_element_with_subpattern_in_map_pattern
+
+_A rest element in a map pattern can't have a subpattern._
+
+#### Description
+
+The analyzer produces this diagnostic when a rest pattern in a map pattern
+has a subpattern. In Dart, there is no notion of a subset of a map, so
+there isn't anything to match against the subpattern.
+
+#### Example
+
+The following code produces this diagnostic because the rest pattern has a
+subpattern:
+
+{% prettify dart tag=pre+code %}
+void f(Map<String, int> m) {
+  switch (m) {
+    case {'a': var a, ... [!> 0!]}:
+      print(a);
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+Remove the subpattern:
+
+{% prettify dart tag=pre+code %}
+void f(Map<String, int> m) {
+  switch (m) {
+    case {'a': var a, ...}:
+      print(a);
+  }
+}
+{% endprettify %}
+
 ### rethrow_outside_catch
 
 _A rethrow must be inside of a catch clause._
@@ -16672,7 +18409,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   [!@Array(8, 8)!]
   external Array<Array<Array<Uint8>>> a0;
 }
@@ -16686,7 +18423,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Array(8, 8, 4)
   external Array<Array<Array<Uint8>>> a0;
 }
@@ -16697,7 +18434,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Array(8, 8)
   external Array<Array<Uint8>> a0;
 }
@@ -16756,6 +18493,40 @@
 int f(C c) => c.b;
 {% endprettify %}
 
+### subtype_of_base_or_final_is_not_base_final_or_sealed
+
+_The type '{0}' must be 'base', 'final' or 'sealed' because the supertype '{1}'
+is 'base'._
+
+_The type '{0}' must be 'base', 'final' or 'sealed' because the supertype '{1}'
+is 'final'._
+
+#### Description
+
+The analyzer produces this diagnostic when a class or mixin has a direct
+or indirect supertype that is either `base` or `final`, but the class or
+mixin itself isn't marked either `base`, `final`, or `sealed`.
+
+#### Example
+
+The following code produces this diagnostic because the class `B` is a
+subtype of `A`, and `A` is a `base` class, but `B` is neither `base`,
+`final` or `sealed`:
+
+{% prettify dart tag=pre+code %}
+base class A {}
+class [!B!] extends A {}
+{% endprettify %}
+
+#### Common fixes
+
+Add either `base`, `final` or `sealed` to the class or mixin declaration:
+
+{% prettify dart tag=pre+code %}
+base class A {}
+final class B extends A {}
+{% endprettify %}
+
 ### subtype_of_deferred_class
 
 _Classes and mixins can't implement deferred classes._
@@ -16772,12 +18543,12 @@
 classes from deferred libraries aren't compiled until the library is
 loaded.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
-Given a file (`a.dart`) that defines the class `A`:
+Given a file `a.dart` that defines the class `A`:
 
 {% prettify dart tag=pre+code %}
 class A {}
@@ -16891,7 +18662,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends [!Double!] {}
+final class C extends [!Double!] {}
 {% endprettify %}
 
 #### Common fixes
@@ -16902,7 +18673,7 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class C extends Struct {
+final class C extends Struct {
   @Int32()
   external int i;
 }
@@ -16912,7 +18683,7 @@
 references to FFI classes:
 
 {% prettify dart tag=pre+code %}
-class C {}
+final class C {}
 {% endprettify %}
 
 ### subtype_of_sealed_class
@@ -16993,11 +18764,11 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class S extends Struct {
+final class S extends Struct {
   external Pointer f;
 }
 
-class C extends [!S!] {
+final class C extends [!S!] {
   external Pointer g;
 }
 {% endprettify %}
@@ -17011,11 +18782,11 @@
 {% prettify dart tag=pre+code %}
 import 'dart:ffi';
 
-class S extends Struct {
+final class S extends Struct {
   external Pointer f;
 }
 
-class C extends Struct {
+final class C extends Struct {
   external Pointer f;
 
   external Pointer g;
@@ -17658,7 +19429,7 @@
 
 #### Common fixes
 
-Add an explicit null check to the expression:
+Add an explicit null-check to the expression:
 
 {% prettify dart tag=pre+code %}
 void f(String? s) {
@@ -17752,8 +19523,8 @@
 is a type declared in a library that is imported using a deferred import.
 These types are required to be available at compile time, but aren't.
 
-For more information, see the language tour's coverage of
-[deferred loading](https://dart.dev/guides/language/language-tour#lazily-loading-a-library).
+For more information, check out
+[Lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library).
 
 #### Example
 
@@ -19328,13 +21099,13 @@
 
 #### Example
 
-Given a file named `a.dart` that contains the following:
+Given a file `a.dart` that contains the following:
 
 {% prettify dart tag=pre+code %}
 class A {}
 {% endprettify %}
 
-And, given a file named `b.dart` that contains the following:
+And, given a file `b.dart` that contains the following:
 
 {% prettify dart tag=pre+code %}
 export 'a.dart';
@@ -19475,6 +21246,68 @@
 class B extends A {}
 {% endprettify %}
 
+### unnecessary_null_assert_pattern
+
+_The null-assert pattern will have no effect because the matched type isn't
+nullable._
+
+#### Description
+
+The analyzer produces this diagnostic when a null-assert pattern is used
+to match a value that isn't nullable.
+
+#### Example
+
+The following code produces this diagnostic because the variable `x` isn't
+nullable:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case var a[!!!] when a > 0) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+Remove the null-assert pattern:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case var a when a > 0) {}
+}
+{% endprettify %}
+
+### unnecessary_null_check_pattern
+
+_The null-check pattern will have no effect because the matched type isn't
+nullable._
+
+#### Description
+
+The analyzer produces this diagnostic when a null-check pattern is used to
+match a value that isn't nullable.
+
+#### Example
+
+The following code produces this diagnostic because the value `x` isn't
+nullable:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case var a[!?!] when a > 0) {}
+}
+{% endprettify %}
+
+#### Common fixes
+
+Remove the null-check pattern:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  if (x case var a when a > 0) {}
+}
+{% endprettify %}
+
 ### unnecessary_null_comparison
 
 _The operand can't be null, so the condition is always 'false'._
@@ -19560,6 +21393,47 @@
 dynamic x;
 {% endprettify %}
 
+### unnecessary_set_literal
+
+_Braces unnecessarily wrap this expression in a set literal._
+
+#### Description
+
+The analyzer produces this diagnostic when a function that has a return
+type of `void`, `Future<void>`, or `FutureOr<void>` uses an expression
+function body (`=>`) and the returned value is a literal set containing a
+single element.
+
+Although the language allows it, returning a value from a `void` function
+isn't useful because it can't be used at the call site. In this particular
+case the return is often due to a misunderstanding about the syntax. The
+braces aren't necessary and can be removed.
+
+#### Example
+
+The following code produces this diagnostic because the closure being
+passed to `g` has a return type of `void`, but is returning a set:
+
+{% prettify dart tag=pre+code %}
+void f() {
+  g(() => [!{1}!]);
+}
+
+void g(void Function() p) {}
+{% endprettify %}
+
+#### Common fixes
+
+Remove the braces from around the value:
+
+{% prettify dart tag=pre+code %}
+void f() {
+  g(() => 1);
+}
+
+void g(void Function() p) {}
+{% endprettify %}
+
 ### unnecessary_type_check
 
 _Unnecessary type check; the result is always 'false'._
@@ -19700,6 +21574,47 @@
 }
 {% endprettify %}
 
+### unreachable_switch_case
+
+_This case is covered by the previous cases._
+
+#### Description
+
+The analyzer produces this diagnostic when a `case` clause in a `switch`
+statement doesn't match anything because all of the matchable values are
+matched by an earlier `case` clause.
+
+#### Example
+
+The following code produces this diagnostic because the value `1` was
+matched in the preceeding case:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  switch (x) {
+    case 1:
+      print('one');
+    [!case!] 1:
+      print('two');
+  }
+}
+{% endprettify %}
+
+#### Common fixes
+
+Change one or both of the conflicting cases to match different values:
+
+{% prettify dart tag=pre+code %}
+void f(int x) {
+  switch (x) {
+    case 1:
+      print('one');
+    case 2:
+      print('two');
+  }
+}
+{% endprettify %}
+
 ### unused_catch_clause
 
 _The exception variable '{0}' isn't used, so the 'catch' clause can be removed._
@@ -20256,6 +22171,39 @@
 }
 {% endprettify %}
 
+### variable_pattern_keyword_in_declaration_context
+
+_Variable patterns in declaration context can't specify 'var' or 'final'
+keyword._
+
+#### Description
+
+The analyzer produces this diagnostic when a variable pattern is used
+within a declaration context.
+
+#### Example
+
+The following code produces this diagnostic because the variable patterns
+in the record pattern are in a declaration context:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  var ([!var!] x, y) = r;
+  print(x + y);
+}
+{% endprettify %}
+
+#### Common fixes
+
+Remove the `var` or `final` keyword(s) within the variable pattern:
+
+{% prettify dart tag=pre+code %}
+void f((int, int) r) {
+  var (x, y) = r;
+  print(x + y);
+}
+{% endprettify %}
+
 ### variable_type_mismatch
 
 _A value of type '{0}' can't be assigned to a const variable of type '{1}'._
diff --git a/analyzer/tool/diagnostics/generate.dart b/analyzer/tool/diagnostics/generate.dart
index e8c353a..8988d02 100644
--- a/analyzer/tool/diagnostics/generate.dart
+++ b/analyzer/tool/diagnostics/generate.dart
@@ -196,6 +196,7 @@
 
 [ffi]: https://dart.dev/guides/libraries/c-interop
 [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_754
+[irrefutable pattern]: https://dart.dev/resources/glossary#irrefutable-pattern
 [meta-doNotStore]: https://pub.dev/documentation/meta/latest/meta/doNotStore-constant.html
 [meta-factory]: https://pub.dev/documentation/meta/latest/meta/factory-constant.html
 [meta-immutable]: https://pub.dev/documentation/meta/latest/meta/immutable-constant.html
@@ -208,6 +209,7 @@
 [meta-UseResult]: https://pub.dev/documentation/meta/latest/meta/UseResult-class.html
 [meta-visibleForOverriding]: https://pub.dev/documentation/meta/latest/meta/visibleForOverriding-constant.html
 [meta-visibleForTesting]: https://pub.dev/documentation/meta/latest/meta/visibleForTesting-constant.html
+[refutable pattern]: https://dart.dev/resources/glossary#refutable-pattern
 ''');
     var errorCodes = infoByName.keys.toList();
     errorCodes.sort();
diff --git a/analyzer/tool/summary/generate.dart b/analyzer/tool/summary/generate.dart
index 653f242..4dfecb9 100644
--- a/analyzer/tool/summary/generate.dart
+++ b/analyzer/tool/summary/generate.dart
@@ -1035,7 +1035,7 @@
   void generate() {
     String name = cls.name;
     String mixinName = '_${name}Mixin';
-    out('abstract class $mixinName implements ${idlPrefix(name)} {');
+    out('mixin $mixinName implements ${idlPrefix(name)} {');
     indent(() {
       String jsonCondition(idl_model.FieldType type, String name) {
         if (type.isList) {
diff --git a/async/BUILD.gn b/async/BUILD.gn
index 20f656a..ac62820 100644
--- a/async/BUILD.gn
+++ b/async/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for async-2.10.0
+# This file is generated by package_importer.py for async-2.11.0
 
 import("//build/dart/dart_library.gni")
 
diff --git a/async/CHANGELOG.md b/async/CHANGELOG.md
index 1b3144c..573d87f 100644
--- a/async/CHANGELOG.md
+++ b/async/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 2.11.0
+
+* Add `CancelableOperation.fromValue`.
+* Add `StreamExtensions.listenAndBuffer`, which buffers events from a stream
+  before it has a listener.
+
 ## 2.10.0
 
 * Add `CancelableOperation.thenOperation` which gives more flexibility to
diff --git a/async/README.md b/async/README.md
index b25b740..be9ed20 100644
--- a/async/README.md
+++ b/async/README.md
@@ -5,6 +5,8 @@
 Contains utility classes in the style of `dart:async` to work with asynchronous
 computations.
 
+## Package API
+
 * The [`AsyncCache`][AsyncCache] class allows expensive asynchronous
   computations values to be cached for a period of time.
 
@@ -90,3 +92,8 @@
 [StreamZip]: https://pub.dev/documentation/async/latest/async/StreamZip-class.html
 [SubscriptionStream]: https://pub.dev/documentation/async/latest/async/SubscriptionStream-class.html
 [typedStreamTransformer]: https://pub.dev/documentation/async/latest/async/typedStreamTransformer.html
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
diff --git a/async/lib/src/cancelable_operation.dart b/async/lib/src/cancelable_operation.dart
index 443ee78..09997e0 100644
--- a/async/lib/src/cancelable_operation.dart
+++ b/async/lib/src/cancelable_operation.dart
@@ -25,7 +25,7 @@
   /// If [onCancel] returns a [Future], it will be returned by [cancel].
   ///
   /// The [onCancel] funcion will be called synchronously
-  /// when the new operation is canceled, and will be called at most once.\
+  /// when the new operation is canceled, and will be called at most once.
   ///
   /// Calling this constructor is equivalent to creating a
   /// [CancelableCompleter] and completing it with [result].
@@ -33,6 +33,15 @@
           {FutureOr Function()? onCancel}) =>
       (CancelableCompleter<T>(onCancel: onCancel)..complete(result)).operation;
 
+  /// Creates a [CancelableOperation] which completes to [value].
+  ///
+  /// Canceling this operation does nothing.
+  ///
+  /// Calling this constructor is equivalent to creating a
+  /// [CancelableCompleter] and completing it with [value].
+  factory CancelableOperation.fromValue(T value) =>
+      (CancelableCompleter<T>()..complete(value)).operation;
+
   /// Creates a [CancelableOperation] wrapping [subscription].
   ///
   /// This overrides [StreamSubscription.onDone] and
diff --git a/async/lib/src/stream_extensions.dart b/async/lib/src/stream_extensions.dart
index 13811e4..4ba9254 100644
--- a/async/lib/src/stream_extensions.dart
+++ b/async/lib/src/stream_extensions.dart
@@ -54,4 +54,28 @@
     });
     return completer.future;
   }
+
+  /// Eagerly listens to this stream and buffers events until needed.
+  ///
+  /// The returned stream will emit the same events as this stream, starting
+  /// from when this method is called. The events are delayed until the returned
+  /// stream is listened to, at which point all buffered events will be emitted
+  /// in order, and then further events from this stream will be emitted as they
+  /// arrive.
+  ///
+  /// The buffer will retain all events until the returned stream is listened
+  /// to, so if the stream can emit arbitrary amounts of data, callers should be
+  /// careful to listen to the stream eventually or call
+  /// `stream.listen(null).cancel()` to discard the buffered data if it becomes
+  /// clear that the data isn't not needed.
+  Stream<T> listenAndBuffer() {
+    var controller = StreamController<T>(sync: true);
+    var subscription = listen(controller.add,
+        onError: controller.addError, onDone: controller.close);
+    controller
+      ..onPause = subscription.pause
+      ..onResume = subscription.resume
+      ..onCancel = subscription.cancel;
+    return controller.stream;
+  }
 }
diff --git a/async/pubspec.yaml b/async/pubspec.yaml
index 5ff1cb5..61dd362 100644
--- a/async/pubspec.yaml
+++ b/async/pubspec.yaml
@@ -1,6 +1,5 @@
 name: async
-version: 2.10.0
-
+version: 2.11.0
 description: Utility functions and classes related to the 'dart:async' library.
 repository: https://github.com/dart-lang/async
 
diff --git a/build/BUILD.gn b/build/BUILD.gn
index 57db34c..3129e78 100644
--- a/build/BUILD.gn
+++ b/build/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for build-2.3.1
+# This file is generated by package_importer.py for build-2.4.0
 
 import("//build/dart/dart_library.gni")
 
 dart_library("build") {
   package_name = "build"
 
-  language_version = "2.17"
+  language_version = "2.18"
 
   disable_analysis = true
 
@@ -17,6 +17,7 @@
     "//third_party/dart-pkg/pub/glob",
     "//third_party/dart-pkg/pub/logging",
     "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/package_config",
     "//third_party/dart-pkg/pub/path",
   ]
 
diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md
index 2bdb11f..38d6c97 100644
--- a/build/CHANGELOG.md
+++ b/build/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.4.0
+
+- Add `BuildStep.packageConfig` getter to resolve a package config of all
+  packages involved in the current build.
+
 ## 2.3.1
 
 - Allow the latest `package:analyzer`.
diff --git a/build/lib/src/analyzer/resolver.dart b/build/lib/src/analyzer/resolver.dart
index 0d437cd..ed549ba 100644
--- a/build/lib/src/analyzer/resolver.dart
+++ b/build/lib/src/analyzer/resolver.dart
@@ -18,10 +18,15 @@
   /// or is a `part of` file (not a standalone Dart library).
   Future<bool> isLibrary(AssetId assetId);
 
-  /// All libraries recursively accessible from the entry point or subsequent
-  /// calls to [libraryFor] and [isLibrary].
+  /// All libraries resolved by this resolver.
   ///
-  /// **NOTE**: This includes all Dart SDK libraries as well.
+  /// This includes the following libraries:
+  ///  - The primary input of this resolver (in other words, the
+  ///   [BuildStep.inputId] of a build step).
+  ///  - Libraries resolved with a direct [libraryFor] call.
+  ///  - Every public `dart:` library part of the SDK.
+  ///  - All libraries recursively accessible from the mentioned sources, for
+  ///    instance because due to imports or exports.
   Stream<LibraryElement> get libraries;
 
   /// Returns the parsed [AstNode] for [Element].
@@ -58,8 +63,9 @@
   /// Returns the first resolved library identified by [libraryName].
   ///
   /// A library is resolved if it's recursively accessible from the entry point
-  /// or subsequent calls to [libraryFor] and [isLibrary]. If no library can be
-  /// found, returns `null`.
+  /// or subsequent calls to [libraryFor]. In other words, this searches for
+  /// libraries in [libraries].
+  /// If no library can be found, returns `null`.
   ///
   /// **NOTE**: In general, its recommended to use [libraryFor] with an absolute
   /// asset id instead of a named identifier that has the possibility of not
diff --git a/build/lib/src/builder/build_step.dart b/build/lib/src/builder/build_step.dart
index 993c28b..3a003e1 100644
--- a/build/lib/src/builder/build_step.dart
+++ b/build/lib/src/builder/build_step.dart
@@ -6,6 +6,7 @@
 
 import 'package:analyzer/dart/element/element.dart';
 import 'package:meta/meta.dart';
+import 'package:package_config/package_config_types.dart';
 
 import '../analyzer/resolver.dart';
 import '../asset/id.dart';
@@ -118,6 +119,17 @@
   /// `InvalidOutputException` when attempting to write an asset not part of
   /// the [allowedOutputs].
   Iterable<AssetId> get allowedOutputs;
+
+  /// Returns a [PackageConfig] resolvable from this build step.
+  ///
+  /// The package config contains all packages involved in the build. Typically,
+  /// this is the package config taken from the current isolate.
+  ///
+  /// The returned package config does not use `file:/`-based URIs and can't be
+  /// used to access package contents with `dart:io`. Instead, packages resolve
+  /// to `asset:/` URIs that can be parsed with [AssetId.resolve] and read with
+  /// [readAsBytes] or [readAsString].
+  Future<PackageConfig> get packageConfig;
 }
 
 abstract class StageTracker {
diff --git a/build/lib/src/builder/build_step_impl.dart b/build/lib/src/builder/build_step_impl.dart
index 4c4aa0a..9aa8078 100644
--- a/build/lib/src/builder/build_step_impl.dart
+++ b/build/lib/src/builder/build_step_impl.dart
@@ -10,6 +10,7 @@
 import 'package:async/async.dart';
 import 'package:crypto/crypto.dart';
 import 'package:glob/glob.dart';
+import 'package:package_config/package_config_types.dart';
 
 import '../analyzer/resolver.dart';
 import '../asset/exceptions.dart';
@@ -60,8 +61,17 @@
 
   final void Function(Iterable<AssetId>)? _reportUnusedAssets;
 
-  BuildStepImpl(this.inputId, Iterable<AssetId> expectedOutputs, this._reader,
-      this._writer, this._resolvers, this._resourceManager,
+  final Future<PackageConfig> Function() _resolvePackageConfig;
+  Future<Result<PackageConfig>>? _resolvedPackageConfig;
+
+  BuildStepImpl(
+      this.inputId,
+      Iterable<AssetId> expectedOutputs,
+      this._reader,
+      this._writer,
+      this._resolvers,
+      this._resourceManager,
+      this._resolvePackageConfig,
       {StageTracker? stageTracker,
       void Function(Iterable<AssetId>)? reportUnusedAssets})
       : allowedOutputs = UnmodifiableSetView(expectedOutputs.toSet()),
@@ -69,6 +79,14 @@
         _reportUnusedAssets = reportUnusedAssets;
 
   @override
+  Future<PackageConfig> get packageConfig async {
+    final resolved =
+        _resolvedPackageConfig ??= Result.capture(_resolvePackageConfig());
+
+    return (await resolved).asFuture;
+  }
+
+  @override
   Resolver get resolver {
     if (_isComplete) throw BuildStepCompletedException();
     final resolvers = _resolvers;
diff --git a/build/lib/src/builder/builder.dart b/build/lib/src/builder/builder.dart
index 8448f1d..ea15d98 100644
--- a/build/lib/src/builder/builder.dart
+++ b/build/lib/src/builder/builder.dart
@@ -54,6 +54,7 @@
   ///
   /// The `isRoot` value will also be overridden to value from [other].
   BuilderOptions overrideWith(BuilderOptions? other) {
+    // ignore: avoid_returning_this
     if (other == null) return this;
     return BuilderOptions(
         {}
diff --git a/build/lib/src/generate/run_builder.dart b/build/lib/src/generate/run_builder.dart
index 880da19..f0c882e 100644
--- a/build/lib/src/generate/run_builder.dart
+++ b/build/lib/src/generate/run_builder.dart
@@ -1,7 +1,10 @@
 // Copyright (c) 2017, 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:isolate';
+
 import 'package:logging/logging.dart';
+import 'package:package_config/package_config.dart';
 
 import '../analyzer/resolver.dart';
 import '../asset/id.dart';
@@ -28,22 +31,50 @@
 /// If [reportUnusedAssetsForInput] is provided then all calls to
 /// `BuildStep.reportUnusedAssets` in [builder] will be forwarded to this
 /// function with the associated primary input.
-Future<void> runBuilder(Builder builder, Iterable<AssetId> inputs,
-    AssetReader reader, AssetWriter writer, Resolvers? resolvers,
-    {Logger? logger,
-    ResourceManager? resourceManager,
-    StageTracker stageTracker = NoOpStageTracker.instance,
-    void Function(AssetId input, Iterable<AssetId> assets)?
-        reportUnusedAssetsForInput}) async {
+Future<void> runBuilder(
+  Builder builder,
+  Iterable<AssetId> inputs,
+  AssetReader reader,
+  AssetWriter writer,
+  Resolvers? resolvers, {
+  Logger? logger,
+  ResourceManager? resourceManager,
+  StageTracker stageTracker = NoOpStageTracker.instance,
+  void Function(AssetId input, Iterable<AssetId> assets)?
+      reportUnusedAssetsForInput,
+  PackageConfig? packageConfig,
+}) async {
   var shouldDisposeResourceManager = resourceManager == null;
   final resources = resourceManager ?? ResourceManager();
   logger ??= Logger('runBuilder');
+
+  PackageConfig? transformedConfig;
+
+  Future<PackageConfig> loadPackageConfig() async {
+    if (transformedConfig != null) return transformedConfig!;
+
+    var config = packageConfig;
+    if (config == null) {
+      final uri = await Isolate.packageConfig;
+
+      if (uri == null) {
+        throw UnsupportedError(
+            'Isolate running the build does not have a package config and no '
+            'fallback has been provided');
+      }
+
+      config = await loadPackageConfigUri(uri);
+    }
+
+    return transformedConfig = config.transformToAssetUris();
+  }
+
   //TODO(nbosch) check overlapping outputs?
   Future<void> buildForInput(AssetId input) async {
     var outputs = expectedOutputs(builder, input);
     if (outputs.isEmpty) return;
     var buildStep = BuildStepImpl(
-        input, outputs, reader, writer, resolvers, resources,
+        input, outputs, reader, writer, resolvers, resources, loadPackageConfig,
         stageTracker: stageTracker,
         reportUnusedAssets: reportUnusedAssetsForInput == null
             ? null
@@ -62,3 +93,28 @@
     await resources.beforeExit();
   }
 }
+
+extension on Package {
+  static final _lib = Uri.parse('lib/');
+
+  Package transformToAssetUris() {
+    return Package(
+      name,
+      Uri(scheme: 'asset', pathSegments: [name, '']),
+      packageUriRoot: _lib,
+      extraData: extraData,
+      languageVersion: languageVersion,
+    );
+  }
+}
+
+extension on PackageConfig {
+  PackageConfig transformToAssetUris() {
+    return PackageConfig(
+      [
+        for (final package in packages) package.transformToAssetUris(),
+      ],
+      extraData: extraData,
+    );
+  }
+}
diff --git a/build/pubspec.yaml b/build/pubspec.yaml
index 11ccb90..3616706 100644
--- a/build/pubspec.yaml
+++ b/build/pubspec.yaml
@@ -1,10 +1,10 @@
 name: build
-version: 2.3.1
+version: 2.4.0
 description: A package for authoring build_runner compatible code generators.
 repository: https://github.com/dart-lang/build/tree/master/build
 
 environment:
-  sdk: ">=2.17.0 <3.0.0"
+  sdk: ">=2.18.0 <3.0.0"
 
 dependencies:
   analyzer: ">=1.5.0 <6.0.0"
@@ -14,6 +14,7 @@
   glob: ^2.0.0
   logging: ^1.0.0
   meta: ^1.3.0
+  package_config: ^2.1.0
   path: ^1.8.0
 
 dev_dependencies:
diff --git a/built_value/BUILD.gn b/built_value/BUILD.gn
index c1899f5..391b56d 100644
--- a/built_value/BUILD.gn
+++ b/built_value/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for built_value-8.4.3
+# This file is generated by package_importer.py for built_value-8.4.4
 
 import("//build/dart/dart_library.gni")
 
diff --git a/built_value/CHANGELOG.md b/built_value/CHANGELOG.md
index 807146c..2e8a796 100644
--- a/built_value/CHANGELOG.md
+++ b/built_value/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Changelog
 
+# 8.4.4
+
+- Increase minimum version of `analyzer`.
+- Fix generation with the type `Never`.
+
 # 8.4.3
 
 - Fix generated deserialization code when there is a manually written builder
diff --git a/built_value/pubspec.yaml b/built_value/pubspec.yaml
index 8069b52..658e5e5 100644
--- a/built_value/pubspec.yaml
+++ b/built_value/pubspec.yaml
@@ -1,5 +1,5 @@
 name: built_value
-version: 8.4.3
+version: 8.4.4
 description: >
   Value types with builders, Dart classes as enums, and serialization.
   This library is the runtime dependency.
diff --git a/checked_yaml/BUILD.gn b/checked_yaml/BUILD.gn
index c4adb9c..dff0515 100644
--- a/checked_yaml/BUILD.gn
+++ b/checked_yaml/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for checked_yaml-2.0.2
+# This file is generated by package_importer.py for checked_yaml-2.0.3
 
 import("//build/dart/dart_library.gni")
 
 dart_library("checked_yaml") {
   package_name = "checked_yaml"
 
-  language_version = "2.18"
+  language_version = "2.19"
 
   disable_analysis = true
 
diff --git a/checked_yaml/CHANGELOG.md b/checked_yaml/CHANGELOG.md
index 1b5ac87..bcecf04 100644
--- a/checked_yaml/CHANGELOG.md
+++ b/checked_yaml/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.0.3
+
+- Require Dart 2.19
+- Add topics
+
 ## 2.0.2
 
 - Require `json_annotation` `^4.3.0`
diff --git a/checked_yaml/pubspec.yaml b/checked_yaml/pubspec.yaml
index 9453825..1b2e74e 100644
--- a/checked_yaml/pubspec.yaml
+++ b/checked_yaml/pubspec.yaml
@@ -1,12 +1,19 @@
 name: checked_yaml
-version: 2.0.2
+version: 2.0.3
 
 description: >-
   Generate more helpful exceptions when decoding YAML documents using
   package:json_serializable and package:yaml.
 repository: https://github.com/google/json_serializable.dart/tree/master/checked_yaml
+topics:
+ - yaml
+ - json
+ - build-runner
+ - json-serializable
+ - codegen
+
 environment:
-  sdk: '>=2.18.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
   json_annotation: ^4.3.0
@@ -16,8 +23,8 @@
 dev_dependencies:
   build_runner: ^2.0.0
   build_verify: ^3.0.0
+  dart_flutter_team_lints: ^1.0.0
   json_serializable: ^6.0.0
-  lints: ^2.0.0
   path: ^1.0.0
   test: ^1.16.0
   test_process: ^2.0.0
diff --git a/code_builder/BUILD.gn b/code_builder/BUILD.gn
index 0220dea..e464d3b 100644
--- a/code_builder/BUILD.gn
+++ b/code_builder/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for code_builder-4.4.0
+# This file is generated by package_importer.py for code_builder-4.5.0
 
 import("//build/dart/dart_library.gni")
 
 dart_library("code_builder") {
   package_name = "code_builder"
 
-  language_version = "2.17"
+  language_version = "2.19"
 
   disable_analysis = true
 
@@ -56,6 +56,8 @@
     "src/specs/reference.dart",
     "src/specs/type_function.dart",
     "src/specs/type_function.g.dart",
+    "src/specs/type_record.dart",
+    "src/specs/type_record.g.dart",
     "src/specs/type_reference.dart",
     "src/specs/type_reference.g.dart",
     "src/specs/typedef.dart",
diff --git a/code_builder/CHANGELOG.md b/code_builder/CHANGELOG.md
index 66aee3b..6065403 100644
--- a/code_builder/CHANGELOG.md
+++ b/code_builder/CHANGELOG.md
@@ -1,3 +1,28 @@
+## 4.5.0
+
+* Require Dart 2.19
+* Add support for emitting type parameters for typedefs.
+* Add support for class modifiers.
+* Add support for records (both types and record literals).
+* Add `literalSpread` and `literalNullSafeSpread` to support adding spreads to
+  `literalMap`.
+
+```dart
+void main() {
+  // Creates a map
+  // {
+  //   ...one,
+  //   2: two,
+  //   ...?three,
+  // }
+  final map = literalMap({
+    literalSpread(): refer('one'),
+    2: refer('two'),
+    literalNullSafeSpread(): refer('three'),
+  });
+}
+```
+
 ## 4.4.0
 
 * Mention how the `allocator` argument relates to imports in the `DartEmitter`
@@ -26,7 +51,7 @@
 * Add `declareConst`, `declareFinal`, and `declareVar` to replace
   `Expression.assignConst`, `assignFinal`, and `assignVar`. Add support for late
   variables with the `declare*` utilities.
-* Add `ParameterBuilder.toSuper` so suport super formal parameters language
+* Add `ParameterBuilder.toSuper` so support super formal parameters language
   feature.
 
 ## 4.1.0
@@ -41,7 +66,7 @@
 ## 4.0.0
 
 * Migrate to null safety.
-* Changed the DartEmittor constructor to use named optional parameters.
+* Changed the `DartEmitter` constructor to use named optional parameters.
 * Add `ParenthesizedExpression` and
   `ExpressionVisitor.visitParenthesizedExpression.`
 
diff --git a/code_builder/README.md b/code_builder/README.md
index 9ee994b..2d77a25 100644
--- a/code_builder/README.md
+++ b/code_builder/README.md
@@ -85,7 +85,7 @@
 > format this repository. You can run it simply from the command-line:
 >
 > ```sh
-> $ dart pub run dart_style:format -w .
+> $ dart run dart_style:format -w .
 > ```
 
 [wiki]: https://github.com/dart-lang/code_builder/wiki
diff --git a/code_builder/analysis_options.yaml b/code_builder/analysis_options.yaml
index 9d6246a..23e98e8 100644
--- a/code_builder/analysis_options.yaml
+++ b/code_builder/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:lints/recommended.yaml
+include: package:dart_flutter_team_lints/analysis_options.yaml
 
 analyzer:
   language:
@@ -8,39 +8,24 @@
 
 linter:
   rules:
-    - always_declare_return_types
-    - avoid_catching_errors
     - avoid_private_typedef_functions
     - avoid_redundant_argument_values
     - avoid_unused_constructor_parameters
     - cancel_subscriptions
     - cascade_invocations
     - comment_references
-    - directives_ordering
     - join_return_with_assignment
-    - lines_longer_than_80_chars
     - literal_only_boolean_expressions
     - missing_whitespace_between_adjacent_strings
     - no_adjacent_strings_in_list
     - no_runtimeType_toString
-    - omit_local_variable_types
-    - only_throw_errors
     - package_api_docs
-    - prefer_asserts_in_initializer_lists
     - prefer_const_constructors
     - prefer_const_declarations
     - prefer_expression_function_bodies
     - prefer_final_locals
     - prefer_relative_imports
-    - prefer_single_quotes
-    - sort_pub_dependencies
     - test_types_in_equals
-    - throw_in_finally
-    - type_annotate_public_apis
-    - unawaited_futures
     - unnecessary_await_in_return
-    - unnecessary_lambdas
-    - unnecessary_parenthesis
-    - unnecessary_statements
     - use_string_buffers
     - use_super_parameters
diff --git a/code_builder/lib/code_builder.dart b/code_builder/lib/code_builder.dart
index 0f7a7ba..6cfaf6a 100644
--- a/code_builder/lib/code_builder.dart
+++ b/code_builder/lib/code_builder.dart
@@ -3,20 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 
 export 'src/allocator.dart' show Allocator;
-export 'src/base.dart' show lazySpec, Spec;
+export 'src/base.dart' show Spec, lazySpec;
 export 'src/emitter.dart' show DartEmitter;
-export 'src/matchers.dart' show equalsDart, EqualsDart;
-export 'src/specs/class.dart' show Class, ClassBuilder;
+export 'src/matchers.dart' show EqualsDart, equalsDart;
+export 'src/specs/class.dart' show Class, ClassBuilder, ClassModifier;
 export 'src/specs/code.dart'
-    show lazyCode, Block, BlockBuilder, Code, StaticCode, ScopedCode;
+    show Block, BlockBuilder, Code, ScopedCode, StaticCode, lazyCode;
 export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder;
 export 'src/specs/directive.dart'
-    show Directive, DirectiveType, DirectiveBuilder;
+    show Directive, DirectiveBuilder, DirectiveType;
 export 'src/specs/enum.dart'
     show Enum, EnumBuilder, EnumValue, EnumValueBuilder;
 export 'src/specs/expression.dart'
     show
-        ToCodeExpression,
         BinaryExpression,
         CodeExpression,
         Expression,
@@ -26,22 +25,27 @@
         InvokeExpressionType,
         LiteralExpression,
         LiteralListExpression,
+        ToCodeExpression,
         declareConst,
         declareFinal,
         declareVar,
         literal,
-        literalNull,
-        literalNum,
         literalBool,
-        literalList,
         literalConstList,
-        literalSet,
-        literalConstSet,
-        literalMap,
         literalConstMap,
+        literalConstRecord,
+        literalConstSet,
+        literalFalse,
+        literalList,
+        literalMap,
+        literalNull,
+        literalNullSafeSpread,
+        literalNum,
+        literalRecord,
+        literalSet,
+        literalSpread,
         literalString,
-        literalTrue,
-        literalFalse;
+        literalTrue;
 export 'src/specs/extension.dart' show Extension, ExtensionBuilder;
 export 'src/specs/field.dart' show Field, FieldBuilder, FieldModifier;
 export 'src/specs/library.dart' show Library, LibraryBuilder;
@@ -54,7 +58,8 @@
         Parameter,
         ParameterBuilder;
 export 'src/specs/mixin.dart' show Mixin, MixinBuilder;
-export 'src/specs/reference.dart' show refer, Reference;
+export 'src/specs/reference.dart' show Reference, refer;
 export 'src/specs/type_function.dart' show FunctionType, FunctionTypeBuilder;
+export 'src/specs/type_record.dart' show RecordType, RecordTypeBuilder;
 export 'src/specs/type_reference.dart' show TypeReference, TypeReferenceBuilder;
 export 'src/specs/typedef.dart' show TypeDef, TypeDefBuilder;
diff --git a/code_builder/lib/src/emitter.dart b/code_builder/lib/src/emitter.dart
index 18c8229..21f680d 100644
--- a/code_builder/lib/src/emitter.dart
+++ b/code_builder/lib/src/emitter.dart
@@ -17,6 +17,7 @@
 import 'specs/mixin.dart';
 import 'specs/reference.dart';
 import 'specs/type_function.dart';
+import 'specs/type_record.dart';
 import 'specs/type_reference.dart';
 import 'specs/typedef.dart';
 import 'visitors.dart';
@@ -115,8 +116,23 @@
     for (var a in spec.annotations) {
       visitAnnotation(a, out);
     }
-    if (spec.abstract) {
-      out.write('abstract ');
+
+    void writeModifier() {
+      if (spec.modifier != null) {
+        out.write('${spec.modifier!.name} ');
+      }
+    }
+
+    if (spec.sealed) {
+      out.write('sealed ');
+    } else {
+      if (spec.abstract) {
+        out.write('abstract ');
+      }
+      writeModifier();
+      if (spec.mixin) {
+        out.write('mixin ');
+      }
     }
     out.write('class ${spec.name}');
     visitTypeParameters(spec.types.map((r) => r.type), out);
@@ -164,6 +180,9 @@
       visitAnnotation(a, out);
     }
 
+    if (spec.base) {
+      out.write('base ');
+    }
     out.write('mixin ${spec.name}');
     visitTypeParameters(spec.types.map((r) => r.type), out);
     if (spec.on != null) {
@@ -536,13 +555,45 @@
   }
 
   @override
+  StringSink visitRecordType(RecordType spec, [StringSink? output]) {
+    final out = (output ??= StringBuffer())..write('(');
+    visitAll<Reference>(spec.positionalFieldTypes, out, (spec) {
+      spec.accept(this, out);
+    });
+    if (spec.namedFieldTypes.isNotEmpty) {
+      if (spec.positionalFieldTypes.isNotEmpty) {
+        out.write(', ');
+      }
+      out.write('{');
+      visitAll<MapEntry<String, Reference>>(spec.namedFieldTypes.entries, out,
+          (entry) {
+        entry.value.accept(this, out);
+        out.write(' ${entry.key}');
+      });
+      out.write('}');
+    } else if (spec.positionalFieldTypes.length == 1) {
+      out.write(',');
+    }
+    out.write(')');
+    // It doesn't really make sense to use records without
+    // `_useNullSafetySyntax`, but since code_builder is generally very
+    // permissive, follow it here too.
+    if (_useNullSafetySyntax && (spec.isNullable ?? false)) {
+      out.write('?');
+    }
+    return out;
+  }
+
+  @override
   StringSink visitTypeDef(TypeDef spec, [StringSink? output]) {
     final out = output ??= StringBuffer();
     spec.docs.forEach(out.writeln);
     for (var a in spec.annotations) {
       visitAnnotation(a, out);
     }
-    out.write('typedef ${spec.name} = ');
+    out.write('typedef ${spec.name}');
+    visitTypeParameters(spec.types.map((r) => r.type), out);
+    out.write(' = ');
     spec.definition.accept(this, out);
     out.writeln(';');
     return out;
diff --git a/code_builder/lib/src/specs/class.dart b/code_builder/lib/src/specs/class.dart
index bd847c4..6636d94 100644
--- a/code_builder/lib/src/specs/class.dart
+++ b/code_builder/lib/src/specs/class.dart
@@ -30,6 +30,15 @@
   /// Whether the class is `abstract`.
   bool get abstract;
 
+  /// Whether the class is `sealed`.
+  bool get sealed;
+
+  /// Whether the class is a `mixin class`.
+  bool get mixin;
+
+  /// The class modifier, i.e. `base`, `final`, `interface`.
+  ClassModifier? get modifier;
+
   @override
   BuiltList<Expression> get annotations;
 
@@ -60,6 +69,23 @@
       visitor.visitClass(this, context);
 }
 
+enum ClassModifier {
+  base,
+  final$,
+  interface;
+
+  String get name {
+    switch (this) {
+      case ClassModifier.base:
+        return 'base';
+      case ClassModifier.final$:
+        return 'final';
+      case ClassModifier.interface:
+        return 'interface';
+    }
+  }
+}
+
 abstract class ClassBuilder extends Object
     with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
     implements Builder<Class, ClassBuilder> {
@@ -75,6 +101,15 @@
   /// Whether the class is `abstract`.
   bool abstract = false;
 
+  /// Whether the class is `sealed`.
+  bool sealed = false;
+
+  /// Whether the class is a `mixin class`.
+  bool mixin = false;
+
+  /// The class modifier, i.e. `base`, `final`, `interface`.
+  ClassModifier? modifier;
+
   @override
   ListBuilder<Expression> annotations = ListBuilder<Expression>();
 
diff --git a/code_builder/lib/src/specs/class.g.dart b/code_builder/lib/src/specs/class.g.dart
index 81854a2..423f576 100644
--- a/code_builder/lib/src/specs/class.g.dart
+++ b/code_builder/lib/src/specs/class.g.dart
@@ -10,6 +10,12 @@
   @override
   final bool abstract;
   @override
+  final bool sealed;
+  @override
+  final bool mixin;
+  @override
+  final ClassModifier? modifier;
+  @override
   final BuiltList<Expression> annotations;
   @override
   final BuiltList<String> docs;
@@ -35,6 +41,9 @@
 
   _$Class._(
       {required this.abstract,
+      required this.sealed,
+      required this.mixin,
+      this.modifier,
       required this.annotations,
       required this.docs,
       this.extend,
@@ -47,6 +56,8 @@
       required this.name})
       : super._() {
     BuiltValueNullFieldError.checkNotNull(abstract, r'Class', 'abstract');
+    BuiltValueNullFieldError.checkNotNull(sealed, r'Class', 'sealed');
+    BuiltValueNullFieldError.checkNotNull(mixin, r'Class', 'mixin');
     BuiltValueNullFieldError.checkNotNull(annotations, r'Class', 'annotations');
     BuiltValueNullFieldError.checkNotNull(docs, r'Class', 'docs');
     BuiltValueNullFieldError.checkNotNull(implements, r'Class', 'implements');
@@ -71,6 +82,9 @@
     if (identical(other, this)) return true;
     return other is Class &&
         abstract == other.abstract &&
+        sealed == other.sealed &&
+        mixin == other.mixin &&
+        modifier == other.modifier &&
         annotations == other.annotations &&
         docs == other.docs &&
         extend == other.extend &&
@@ -85,32 +99,32 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc(
-                        $jc(
-                            $jc(
-                                $jc(
-                                    $jc(
-                                        $jc($jc(0, abstract.hashCode),
-                                            annotations.hashCode),
-                                        docs.hashCode),
-                                    extend.hashCode),
-                                implements.hashCode),
-                            mixins.hashCode),
-                        types.hashCode),
-                    constructors.hashCode),
-                methods.hashCode),
-            fields.hashCode),
-        name.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, abstract.hashCode);
+    _$hash = $jc(_$hash, sealed.hashCode);
+    _$hash = $jc(_$hash, mixin.hashCode);
+    _$hash = $jc(_$hash, modifier.hashCode);
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, extend.hashCode);
+    _$hash = $jc(_$hash, implements.hashCode);
+    _$hash = $jc(_$hash, mixins.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, constructors.hashCode);
+    _$hash = $jc(_$hash, methods.hashCode);
+    _$hash = $jc(_$hash, fields.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
   String toString() {
     return (newBuiltValueToStringHelper(r'Class')
           ..add('abstract', abstract)
+          ..add('sealed', sealed)
+          ..add('mixin', mixin)
+          ..add('modifier', modifier)
           ..add('annotations', annotations)
           ..add('docs', docs)
           ..add('extend', extend)
@@ -141,6 +155,42 @@
   }
 
   @override
+  bool get sealed {
+    _$this;
+    return super.sealed;
+  }
+
+  @override
+  set sealed(bool sealed) {
+    _$this;
+    super.sealed = sealed;
+  }
+
+  @override
+  bool get mixin {
+    _$this;
+    return super.mixin;
+  }
+
+  @override
+  set mixin(bool mixin) {
+    _$this;
+    super.mixin = mixin;
+  }
+
+  @override
+  ClassModifier? get modifier {
+    _$this;
+    return super.modifier;
+  }
+
+  @override
+  set modifier(ClassModifier? modifier) {
+    _$this;
+    super.modifier = modifier;
+  }
+
+  @override
   ListBuilder<Expression> get annotations {
     _$this;
     return super.annotations;
@@ -266,6 +316,9 @@
     final $v = _$v;
     if ($v != null) {
       super.abstract = $v.abstract;
+      super.sealed = $v.sealed;
+      super.mixin = $v.mixin;
+      super.modifier = $v.modifier;
       super.annotations = $v.annotations.toBuilder();
       super.docs = $v.docs.toBuilder();
       super.extend = $v.extend;
@@ -302,6 +355,11 @@
           new _$Class._(
               abstract: BuiltValueNullFieldError.checkNotNull(
                   abstract, r'Class', 'abstract'),
+              sealed: BuiltValueNullFieldError.checkNotNull(
+                  sealed, r'Class', 'sealed'),
+              mixin: BuiltValueNullFieldError.checkNotNull(
+                  mixin, r'Class', 'mixin'),
+              modifier: modifier,
               annotations: annotations.build(),
               docs: docs.build(),
               extend: extend,
@@ -344,4 +402,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/code.g.dart b/code_builder/lib/src/specs/code.g.dart
index beb7e5c..7b5ba78 100644
--- a/code_builder/lib/src/specs/code.g.dart
+++ b/code_builder/lib/src/specs/code.g.dart
@@ -32,7 +32,10 @@
 
   @override
   int get hashCode {
-    return $jf($jc(0, statements.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, statements.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -103,4 +106,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/constructor.g.dart b/code_builder/lib/src/specs/constructor.g.dart
index 0713299..3f06932 100644
--- a/code_builder/lib/src/specs/constructor.g.dart
+++ b/code_builder/lib/src/specs/constructor.g.dart
@@ -90,28 +90,21 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc(
-                        $jc(
-                            $jc(
-                                $jc(
-                                    $jc(
-                                        $jc(
-                                            $jc($jc(0, annotations.hashCode),
-                                                docs.hashCode),
-                                            optionalParameters.hashCode),
-                                        requiredParameters.hashCode),
-                                    initializers.hashCode),
-                                body.hashCode),
-                            external.hashCode),
-                        constant.hashCode),
-                    factory.hashCode),
-                lambda.hashCode),
-            name.hashCode),
-        redirect.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, optionalParameters.hashCode);
+    _$hash = $jc(_$hash, requiredParameters.hashCode);
+    _$hash = $jc(_$hash, initializers.hashCode);
+    _$hash = $jc(_$hash, body.hashCode);
+    _$hash = $jc(_$hash, external.hashCode);
+    _$hash = $jc(_$hash, constant.hashCode);
+    _$hash = $jc(_$hash, factory.hashCode);
+    _$hash = $jc(_$hash, lambda.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jc(_$hash, redirect.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -360,4 +353,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/directive.g.dart b/code_builder/lib/src/specs/directive.g.dart
index 598ef16..b28158e 100644
--- a/code_builder/lib/src/specs/directive.g.dart
+++ b/code_builder/lib/src/specs/directive.g.dart
@@ -59,12 +59,15 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc($jc($jc($jc(0, as.hashCode), url.hashCode), type.hashCode),
-                show.hashCode),
-            hide.hashCode),
-        deferred.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, as.hashCode);
+    _$hash = $jc(_$hash, url.hashCode);
+    _$hash = $jc(_$hash, type.hashCode);
+    _$hash = $jc(_$hash, show.hashCode);
+    _$hash = $jc(_$hash, hide.hashCode);
+    _$hash = $jc(_$hash, deferred.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -204,4 +207,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/enum.g.dart b/code_builder/lib/src/specs/enum.g.dart
index 5ed4e50..050885b 100644
--- a/code_builder/lib/src/specs/enum.g.dart
+++ b/code_builder/lib/src/specs/enum.g.dart
@@ -81,22 +81,19 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc(
-                        $jc(
-                            $jc(
-                                $jc($jc($jc(0, name.hashCode), values.hashCode),
-                                    annotations.hashCode),
-                                docs.hashCode),
-                            implements.hashCode),
-                        mixins.hashCode),
-                    types.hashCode),
-                constructors.hashCode),
-            methods.hashCode),
-        fields.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jc(_$hash, values.hashCode);
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, implements.hashCode);
+    _$hash = $jc(_$hash, mixins.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, constructors.hashCode);
+    _$hash = $jc(_$hash, methods.hashCode);
+    _$hash = $jc(_$hash, fields.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -375,14 +372,15 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc($jc($jc(0, name.hashCode), annotations.hashCode),
-                    docs.hashCode),
-                constructorName.hashCode),
-            types.hashCode),
-        arguments.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, constructorName.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, arguments.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -538,4 +536,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/expression.dart b/code_builder/lib/src/specs/expression.dart
index 5400b38..275404e 100644
--- a/code_builder/lib/src/specs/expression.dart
+++ b/code_builder/lib/src/specs/expression.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library code_builder.src.specs.expression;
-
 import 'package:meta/meta.dart';
 
 import '../base.dart';
@@ -149,6 +147,7 @@
       );
 
   /// Returns the result of `this` `-` [other].
+  // TODO(kevmoo): create a function spelled correctly and deprecate this one!
   Expression operatorSubstract(Expression other) => BinaryExpression._(
         expression,
         other,
@@ -314,7 +313,7 @@
         addSpace: false,
       );
 
-  /// This expression precenede by the null safe spread operator `?...`.
+  /// This expression preceded by the null safe spread operator `?...`.
   Expression get nullSafeSpread => BinaryExpression._(
         const LiteralExpression._('...?'),
         this,
@@ -413,6 +412,8 @@
   T visitLiteralListExpression(LiteralListExpression expression, [T? context]);
   T visitLiteralSetExpression(LiteralSetExpression expression, [T? context]);
   T visitLiteralMapExpression(LiteralMapExpression expression, [T? context]);
+  T visitLiteralRecordExpression(LiteralRecordExpression expression,
+      [T? context]);
   T visitParenthesizedExpression(ParenthesizedExpression expression,
       [T? context]);
 }
@@ -593,7 +594,9 @@
       visitAll<Object?>(expression.values.keys, out, (key) {
         final value = expression.values[key];
         _acceptLiteral(key, out);
-        out.write(': ');
+        if (key is! LiteralSpreadExpression) {
+          out.write(': ');
+        }
         _acceptLiteral(value, out);
       });
       if (expression.values.length > 1) {
@@ -604,6 +607,33 @@
   }
 
   @override
+  StringSink visitLiteralRecordExpression(
+    LiteralRecordExpression expression, [
+    StringSink? output,
+  ]) {
+    final out = output ??= StringBuffer();
+    return _writeConstExpression(out, expression.isConst, () {
+      out.write('(');
+      visitAll<Object?>(expression.positionalFieldValues, out, (value) {
+        _acceptLiteral(value, out);
+      });
+      if (expression.namedFieldValues.isNotEmpty) {
+        if (expression.positionalFieldValues.isNotEmpty) {
+          out.write(', ');
+        }
+      } else if (expression.positionalFieldValues.length == 1) {
+        out.write(',');
+      }
+      visitAll<MapEntry<String, Object?>>(
+          expression.namedFieldValues.entries, out, (entry) {
+        out.write('${entry.key}: ');
+        _acceptLiteral(entry.value, out);
+      });
+      return out..write(')');
+    });
+  }
+
+  @override
   StringSink visitParenthesizedExpression(
     ParenthesizedExpression expression, [
     StringSink? output,
diff --git a/code_builder/lib/src/specs/expression/binary.dart b/code_builder/lib/src/specs/expression/binary.dart
index 35e5305..d02a2a6 100644
--- a/code_builder/lib/src/specs/expression/binary.dart
+++ b/code_builder/lib/src/specs/expression/binary.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of code_builder.src.specs.expression;
+part of '../expression.dart';
 
 /// Represents two expressions ([left] and [right]) and an [operator].
 class BinaryExpression extends Expression {
diff --git a/code_builder/lib/src/specs/expression/closure.dart b/code_builder/lib/src/specs/expression/closure.dart
index 0506465..706600c 100644
--- a/code_builder/lib/src/specs/expression/closure.dart
+++ b/code_builder/lib/src/specs/expression/closure.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of code_builder.src.specs.expression;
+part of '../expression.dart';
 
 /// Returns [method] as closure, removing its return type and type parameters.
 Expression toClosure(Method method) {
diff --git a/code_builder/lib/src/specs/expression/code.dart b/code_builder/lib/src/specs/expression/code.dart
index 0eb0c3d..1529edb 100644
--- a/code_builder/lib/src/specs/expression/code.dart
+++ b/code_builder/lib/src/specs/expression/code.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of code_builder.src.specs.expression;
+part of '../expression.dart';
 
 /// Represents a [Code] block as an [Expression].
 class CodeExpression extends Expression {
diff --git a/code_builder/lib/src/specs/expression/invoke.dart b/code_builder/lib/src/specs/expression/invoke.dart
index 198ad10..f54dc0e 100644
--- a/code_builder/lib/src/specs/expression/invoke.dart
+++ b/code_builder/lib/src/specs/expression/invoke.dart
@@ -4,7 +4,7 @@
 
 // ignore_for_file: deprecated_member_use_from_same_package
 
-part of code_builder.src.specs.expression;
+part of '../expression.dart';
 
 /// Represents invoking [target] as a method with arguments.
 class InvokeExpression extends Expression {
diff --git a/code_builder/lib/src/specs/expression/literal.dart b/code_builder/lib/src/specs/expression/literal.dart
index 2d301f5..a305fa6 100644
--- a/code_builder/lib/src/specs/expression/literal.dart
+++ b/code_builder/lib/src/specs/expression/literal.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of code_builder.src.specs.expression;
+part of '../expression.dart';
 
 /// Converts a runtime Dart [literal] value into an [Expression].
 ///
@@ -65,6 +65,20 @@
   return LiteralExpression._("${raw ? 'r' : ''}'$escaped'");
 }
 
+/// Create a literal `...` operator for use when creating a Map literal.
+///
+/// *NOTE* This is used as a sentinel when constructing a `literalMap` or a
+/// or `literalConstMap` to signify that the value should be spread. Do NOT
+/// reuse the value when creating a Map with multiple spreads.
+Expression literalSpread() => LiteralSpreadExpression._(false);
+
+/// Create a literal `...?` operator for use when creating a Map literal.
+///
+/// *NOTE* This is used as a sentinel when constructing a `literalMap` or a
+/// or `literalConstMap` to signify that the value should be spread. Do NOT
+/// reuse the value when creating a Map with multiple spreads.
+Expression literalNullSafeSpread() => LiteralSpreadExpression._(true);
+
 /// Creates a literal list expression from [values].
 LiteralListExpression literalList(Iterable<Object?> values,
         [Reference? type]) =>
@@ -99,6 +113,18 @@
 ]) =>
     LiteralMapExpression._(true, values, keyType, valueType);
 
+/// Create a literal record expression from [positionalFieldValues] and
+/// [namedFieldValues].
+LiteralRecordExpression literalRecord(List<Object?> positionalFieldValues,
+        Map<String, Object?> namedFieldValues) =>
+    LiteralRecordExpression._(false, positionalFieldValues, namedFieldValues);
+
+/// Create a literal `const` record expression from [positionalFieldValues] and
+/// [namedFieldValues].
+LiteralRecordExpression literalConstRecord(List<Object?> positionalFieldValues,
+        Map<String, Object?> namedFieldValues) =>
+    LiteralRecordExpression._(true, positionalFieldValues, namedFieldValues);
+
 /// Represents a literal value in Dart source code.
 ///
 /// For example, `LiteralExpression('null')` should emit `null`.
@@ -122,6 +148,11 @@
   String toString() => literal;
 }
 
+class LiteralSpreadExpression extends LiteralExpression {
+  LiteralSpreadExpression._(bool nullAware)
+      : super._('...${nullAware ? '?' : ''}');
+}
+
 class LiteralListExpression extends Expression {
   @override
   final bool isConst;
@@ -175,3 +206,24 @@
   @override
   String toString() => '{$values}';
 }
+
+class LiteralRecordExpression extends Expression {
+  @override
+  final bool isConst;
+  final List<Object?> positionalFieldValues;
+  final Map<String, Object?> namedFieldValues;
+
+  const LiteralRecordExpression._(
+      this.isConst, this.positionalFieldValues, this.namedFieldValues);
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+      visitor.visitLiteralRecordExpression(this, context);
+
+  @override
+  String toString() {
+    final allFields = positionalFieldValues.map((v) => v.toString()).followedBy(
+        namedFieldValues.entries.map((e) => '${e.key}: ${e.value}'));
+    return '(${allFields.join(', ')})';
+  }
+}
diff --git a/code_builder/lib/src/specs/expression/parenthesized.dart b/code_builder/lib/src/specs/expression/parenthesized.dart
index cc4d468..ea34ae8 100644
--- a/code_builder/lib/src/specs/expression/parenthesized.dart
+++ b/code_builder/lib/src/specs/expression/parenthesized.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of code_builder.src.specs.expression;
+part of '../expression.dart';
 
 /// An [Expression] wrapped with parenthesis.
 class ParenthesizedExpression extends Expression {
diff --git a/code_builder/lib/src/specs/extension.g.dart b/code_builder/lib/src/specs/extension.g.dart
index 7d4afce..83de176 100644
--- a/code_builder/lib/src/specs/extension.g.dart
+++ b/code_builder/lib/src/specs/extension.g.dart
@@ -64,16 +64,16 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc($jc($jc(0, annotations.hashCode), docs.hashCode),
-                        on.hashCode),
-                    types.hashCode),
-                methods.hashCode),
-            fields.hashCode),
-        name.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, on.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, methods.hashCode);
+    _$hash = $jc(_$hash, fields.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -245,4 +245,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/field.g.dart b/code_builder/lib/src/specs/field.g.dart
index 543a053..c8e9f05 100644
--- a/code_builder/lib/src/specs/field.g.dart
+++ b/code_builder/lib/src/specs/field.g.dart
@@ -68,18 +68,17 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc(
-                        $jc($jc($jc(0, annotations.hashCode), docs.hashCode),
-                            assignment.hashCode),
-                        static.hashCode),
-                    late.hashCode),
-                name.hashCode),
-            type.hashCode),
-        modifier.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, assignment.hashCode);
+    _$hash = $jc(_$hash, static.hashCode);
+    _$hash = $jc(_$hash, late.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jc(_$hash, type.hashCode);
+    _$hash = $jc(_$hash, modifier.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -263,4 +262,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/library.g.dart b/code_builder/lib/src/specs/library.g.dart
index f4451ba..6bf19b3 100644
--- a/code_builder/lib/src/specs/library.g.dart
+++ b/code_builder/lib/src/specs/library.g.dart
@@ -61,14 +61,15 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc($jc($jc(0, annotations.hashCode), directives.hashCode),
-                    body.hashCode),
-                comments.hashCode),
-            ignoreForFile.hashCode),
-        name.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, directives.hashCode);
+    _$hash = $jc(_$hash, body.hashCode);
+    _$hash = $jc(_$hash, comments.hashCode);
+    _$hash = $jc(_$hash, ignoreForFile.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -224,4 +225,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/method.g.dart b/code_builder/lib/src/specs/method.g.dart
index f503da8..214f6b2 100644
--- a/code_builder/lib/src/specs/method.g.dart
+++ b/code_builder/lib/src/specs/method.g.dart
@@ -92,32 +92,22 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc(
-                        $jc(
-                            $jc(
-                                $jc(
-                                    $jc(
-                                        $jc(
-                                            $jc(
-                                                $jc(
-                                                    $jc(0,
-                                                        annotations.hashCode),
-                                                    docs.hashCode),
-                                                types.hashCode),
-                                            optionalParameters.hashCode),
-                                        requiredParameters.hashCode),
-                                    body.hashCode),
-                                external.hashCode),
-                            lambda.hashCode),
-                        static.hashCode),
-                    name.hashCode),
-                type.hashCode),
-            modifier.hashCode),
-        returns.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, optionalParameters.hashCode);
+    _$hash = $jc(_$hash, requiredParameters.hashCode);
+    _$hash = $jc(_$hash, body.hashCode);
+    _$hash = $jc(_$hash, external.hashCode);
+    _$hash = $jc(_$hash, lambda.hashCode);
+    _$hash = $jc(_$hash, static.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jc(_$hash, type.hashCode);
+    _$hash = $jc(_$hash, modifier.hashCode);
+    _$hash = $jc(_$hash, returns.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -458,26 +448,20 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc(
-                        $jc(
-                            $jc(
-                                $jc(
-                                    $jc(
-                                        $jc($jc(0, defaultTo.hashCode),
-                                            name.hashCode),
-                                        named.hashCode),
-                                    toThis.hashCode),
-                                toSuper.hashCode),
-                            annotations.hashCode),
-                        docs.hashCode),
-                    types.hashCode),
-                type.hashCode),
-            required.hashCode),
-        covariant.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, defaultTo.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jc(_$hash, named.hashCode);
+    _$hash = $jc(_$hash, toThis.hashCode);
+    _$hash = $jc(_$hash, toSuper.hashCode);
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, type.hashCode);
+    _$hash = $jc(_$hash, required.hashCode);
+    _$hash = $jc(_$hash, covariant.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -710,4 +694,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/mixin.dart b/code_builder/lib/src/specs/mixin.dart
index 475f408..e666bdd 100644
--- a/code_builder/lib/src/specs/mixin.dart
+++ b/code_builder/lib/src/specs/mixin.dart
@@ -26,6 +26,9 @@
 
   Mixin._();
 
+  /// Whether the mixin is a `base mixin`.
+  bool get base;
+
   @override
   BuiltList<Expression> get annotations;
 
@@ -60,6 +63,9 @@
 
   MixinBuilder._();
 
+  /// Whether the mixin is a `base mixin`.
+  bool base = false;
+
   @override
   ListBuilder<Expression> annotations = ListBuilder<Expression>();
 
diff --git a/code_builder/lib/src/specs/mixin.g.dart b/code_builder/lib/src/specs/mixin.g.dart
index ea9700e..28c7356 100644
--- a/code_builder/lib/src/specs/mixin.g.dart
+++ b/code_builder/lib/src/specs/mixin.g.dart
@@ -8,6 +8,8 @@
 
 class _$Mixin extends Mixin {
   @override
+  final bool base;
+  @override
   final BuiltList<Expression> annotations;
   @override
   final BuiltList<String> docs;
@@ -28,7 +30,8 @@
       (new MixinBuilder()..update(updates)).build() as _$Mixin;
 
   _$Mixin._(
-      {required this.annotations,
+      {required this.base,
+      required this.annotations,
       required this.docs,
       this.on,
       required this.implements,
@@ -37,6 +40,7 @@
       required this.fields,
       required this.name})
       : super._() {
+    BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base');
     BuiltValueNullFieldError.checkNotNull(annotations, r'Mixin', 'annotations');
     BuiltValueNullFieldError.checkNotNull(docs, r'Mixin', 'docs');
     BuiltValueNullFieldError.checkNotNull(implements, r'Mixin', 'implements');
@@ -57,6 +61,7 @@
   bool operator ==(Object other) {
     if (identical(other, this)) return true;
     return other is Mixin &&
+        base == other.base &&
         annotations == other.annotations &&
         docs == other.docs &&
         on == other.on &&
@@ -69,23 +74,24 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc(
-                        $jc($jc($jc(0, annotations.hashCode), docs.hashCode),
-                            on.hashCode),
-                        implements.hashCode),
-                    types.hashCode),
-                methods.hashCode),
-            fields.hashCode),
-        name.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, base.hashCode);
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, on.hashCode);
+    _$hash = $jc(_$hash, implements.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, methods.hashCode);
+    _$hash = $jc(_$hash, fields.hashCode);
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
   String toString() {
     return (newBuiltValueToStringHelper(r'Mixin')
+          ..add('base', base)
           ..add('annotations', annotations)
           ..add('docs', docs)
           ..add('on', on)
@@ -102,6 +108,18 @@
   _$Mixin? _$v;
 
   @override
+  bool get base {
+    _$this;
+    return super.base;
+  }
+
+  @override
+  set base(bool base) {
+    _$this;
+    super.base = base;
+  }
+
+  @override
   ListBuilder<Expression> get annotations {
     _$this;
     return super.annotations;
@@ -202,6 +220,7 @@
   MixinBuilder get _$this {
     final $v = _$v;
     if ($v != null) {
+      super.base = $v.base;
       super.annotations = $v.annotations.toBuilder();
       super.docs = $v.docs.toBuilder();
       super.on = $v.on;
@@ -234,6 +253,8 @@
     try {
       _$result = _$v ??
           new _$Mixin._(
+              base:
+                  BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base'),
               annotations: annotations.build(),
               docs: docs.build(),
               on: on,
@@ -270,4 +291,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/reference.dart b/code_builder/lib/src/specs/reference.dart
index 6668e81..06032ad 100644
--- a/code_builder/lib/src/specs/reference.dart
+++ b/code_builder/lib/src/specs/reference.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library code_builder.src.specs.reference;
-
 import 'package:built_value/built_value.dart';
 import 'package:meta/meta.dart';
 
diff --git a/code_builder/lib/src/specs/type_function.g.dart b/code_builder/lib/src/specs/type_function.g.dart
index f9fe0d6..d09f59b 100644
--- a/code_builder/lib/src/specs/type_function.g.dart
+++ b/code_builder/lib/src/specs/type_function.g.dart
@@ -68,16 +68,16 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc(
-            $jc(
-                $jc(
-                    $jc($jc($jc(0, returnType.hashCode), types.hashCode),
-                        requiredParameters.hashCode),
-                    optionalParameters.hashCode),
-                namedParameters.hashCode),
-            namedRequiredParameters.hashCode),
-        isNullable.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, returnType.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, requiredParameters.hashCode);
+    _$hash = $jc(_$hash, optionalParameters.hashCode);
+    _$hash = $jc(_$hash, namedParameters.hashCode);
+    _$hash = $jc(_$hash, namedRequiredParameters.hashCode);
+    _$hash = $jc(_$hash, isNullable.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -249,4 +249,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/type_record.dart b/code_builder/lib/src/specs/type_record.dart
new file mode 100644
index 0000000..2f0594c
--- /dev/null
+++ b/code_builder/lib/src/specs/type_record.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2023, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../visitors.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'type_record.g.dart';
+
+@immutable
+abstract class RecordType extends Expression
+    implements Built<RecordType, RecordTypeBuilder>, Reference, Spec {
+  factory RecordType([
+    void Function(RecordTypeBuilder) updates,
+  ]) = _$RecordType;
+
+  RecordType._();
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R? context,
+  ]) =>
+      visitor.visitRecordType(this, context);
+
+  BuiltList<Reference> get positionalFieldTypes;
+
+  BuiltMap<String, Reference> get namedFieldTypes;
+
+  @override
+  String? get url => null;
+
+  @override
+  String? get symbol => null;
+
+  @override
+  Reference get type => this;
+
+  /// Optional nullability.
+  bool? get isNullable;
+
+  @override
+  Expression newInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot instantiate a record type.');
+
+  @override
+  Expression newInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot instantiate a record type.');
+
+  @override
+  Expression constInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot "const" a record type.');
+
+  @override
+  Expression constInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot "const" a record type.');
+}
+
+abstract class RecordTypeBuilder extends Object
+    implements Builder<RecordType, RecordTypeBuilder> {
+  factory RecordTypeBuilder() = _$RecordTypeBuilder;
+
+  RecordTypeBuilder._();
+
+  ListBuilder<Reference> positionalFieldTypes = ListBuilder<Reference>();
+
+  MapBuilder<String, Reference> namedFieldTypes =
+      MapBuilder<String, Reference>();
+
+  bool? isNullable;
+
+  String? url;
+
+  String? symbol;
+}
diff --git a/code_builder/lib/src/specs/type_record.g.dart b/code_builder/lib/src/specs/type_record.g.dart
new file mode 100644
index 0000000..b1d47df
--- /dev/null
+++ b/code_builder/lib/src/specs/type_record.g.dart
@@ -0,0 +1,159 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'type_record.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$RecordType extends RecordType {
+  @override
+  final BuiltList<Reference> positionalFieldTypes;
+  @override
+  final BuiltMap<String, Reference> namedFieldTypes;
+  @override
+  final bool? isNullable;
+
+  factory _$RecordType([void Function(RecordTypeBuilder)? updates]) =>
+      (new RecordTypeBuilder()..update(updates)).build() as _$RecordType;
+
+  _$RecordType._(
+      {required this.positionalFieldTypes,
+      required this.namedFieldTypes,
+      this.isNullable})
+      : super._() {
+    BuiltValueNullFieldError.checkNotNull(
+        positionalFieldTypes, r'RecordType', 'positionalFieldTypes');
+    BuiltValueNullFieldError.checkNotNull(
+        namedFieldTypes, r'RecordType', 'namedFieldTypes');
+  }
+
+  @override
+  RecordType rebuild(void Function(RecordTypeBuilder) updates) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$RecordTypeBuilder toBuilder() => new _$RecordTypeBuilder()..replace(this);
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) return true;
+    return other is RecordType &&
+        positionalFieldTypes == other.positionalFieldTypes &&
+        namedFieldTypes == other.namedFieldTypes &&
+        isNullable == other.isNullable;
+  }
+
+  @override
+  int get hashCode {
+    var _$hash = 0;
+    _$hash = $jc(_$hash, positionalFieldTypes.hashCode);
+    _$hash = $jc(_$hash, namedFieldTypes.hashCode);
+    _$hash = $jc(_$hash, isNullable.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper(r'RecordType')
+          ..add('positionalFieldTypes', positionalFieldTypes)
+          ..add('namedFieldTypes', namedFieldTypes)
+          ..add('isNullable', isNullable))
+        .toString();
+  }
+}
+
+class _$RecordTypeBuilder extends RecordTypeBuilder {
+  _$RecordType? _$v;
+
+  @override
+  ListBuilder<Reference> get positionalFieldTypes {
+    _$this;
+    return super.positionalFieldTypes;
+  }
+
+  @override
+  set positionalFieldTypes(ListBuilder<Reference> positionalFieldTypes) {
+    _$this;
+    super.positionalFieldTypes = positionalFieldTypes;
+  }
+
+  @override
+  MapBuilder<String, Reference> get namedFieldTypes {
+    _$this;
+    return super.namedFieldTypes;
+  }
+
+  @override
+  set namedFieldTypes(MapBuilder<String, Reference> namedFieldTypes) {
+    _$this;
+    super.namedFieldTypes = namedFieldTypes;
+  }
+
+  @override
+  bool? get isNullable {
+    _$this;
+    return super.isNullable;
+  }
+
+  @override
+  set isNullable(bool? isNullable) {
+    _$this;
+    super.isNullable = isNullable;
+  }
+
+  _$RecordTypeBuilder() : super._();
+
+  RecordTypeBuilder get _$this {
+    final $v = _$v;
+    if ($v != null) {
+      super.positionalFieldTypes = $v.positionalFieldTypes.toBuilder();
+      super.namedFieldTypes = $v.namedFieldTypes.toBuilder();
+      super.isNullable = $v.isNullable;
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(RecordType other) {
+    ArgumentError.checkNotNull(other, 'other');
+    _$v = other as _$RecordType;
+  }
+
+  @override
+  void update(void Function(RecordTypeBuilder)? updates) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  RecordType build() => _build();
+
+  _$RecordType _build() {
+    _$RecordType _$result;
+    try {
+      _$result = _$v ??
+          new _$RecordType._(
+              positionalFieldTypes: positionalFieldTypes.build(),
+              namedFieldTypes: namedFieldTypes.build(),
+              isNullable: isNullable);
+    } catch (_) {
+      late String _$failedField;
+      try {
+        _$failedField = 'positionalFieldTypes';
+        positionalFieldTypes.build();
+        _$failedField = 'namedFieldTypes';
+        namedFieldTypes.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            r'RecordType', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/type_reference.g.dart b/code_builder/lib/src/specs/type_reference.g.dart
index b0f5f0d..124e8b4 100644
--- a/code_builder/lib/src/specs/type_reference.g.dart
+++ b/code_builder/lib/src/specs/type_reference.g.dart
@@ -53,10 +53,14 @@
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc($jc($jc($jc(0, symbol.hashCode), url.hashCode), bound.hashCode),
-            types.hashCode),
-        isNullable.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, symbol.hashCode);
+    _$hash = $jc(_$hash, url.hashCode);
+    _$hash = $jc(_$hash, bound.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jc(_$hash, isNullable.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -190,4 +194,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/specs/typedef.dart b/code_builder/lib/src/specs/typedef.dart
index 1c305ef..fe5d8f1 100644
--- a/code_builder/lib/src/specs/typedef.dart
+++ b/code_builder/lib/src/specs/typedef.dart
@@ -9,14 +9,16 @@
 import '../base.dart';
 import '../mixins/annotations.dart';
 import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
 import '../visitors.dart';
 import 'expression.dart';
+import 'reference.dart';
 
 part 'typedef.g.dart';
 
 @immutable
 abstract class TypeDef extends Object
-    with HasAnnotations, HasDartDocs
+    with HasAnnotations, HasDartDocs, HasGenerics
     implements Built<TypeDef, TypeDefBuilder>, Spec {
   factory TypeDef([void Function(TypeDefBuilder)? updates]) = _$TypeDef;
 
@@ -39,7 +41,7 @@
 }
 
 abstract class TypeDefBuilder extends Object
-    with HasAnnotationsBuilder, HasDartDocsBuilder
+    with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
     implements Builder<TypeDef, TypeDefBuilder> {
   factory TypeDefBuilder() = _$TypeDefBuilder;
 
@@ -51,6 +53,9 @@
   @override
   ListBuilder<String> docs = ListBuilder<String>();
 
+  @override
+  ListBuilder<Reference> types = ListBuilder<Reference>();
+
   String? name;
 
   Expression? definition;
diff --git a/code_builder/lib/src/specs/typedef.g.dart b/code_builder/lib/src/specs/typedef.g.dart
index f884376..8c2a16c 100644
--- a/code_builder/lib/src/specs/typedef.g.dart
+++ b/code_builder/lib/src/specs/typedef.g.dart
@@ -15,6 +15,8 @@
   final BuiltList<Expression> annotations;
   @override
   final BuiltList<String> docs;
+  @override
+  final BuiltList<Reference> types;
 
   factory _$TypeDef([void Function(TypeDefBuilder)? updates]) =>
       (new TypeDefBuilder()..update(updates)).build() as _$TypeDef;
@@ -23,13 +25,15 @@
       {required this.name,
       required this.definition,
       required this.annotations,
-      required this.docs})
+      required this.docs,
+      required this.types})
       : super._() {
     BuiltValueNullFieldError.checkNotNull(name, r'TypeDef', 'name');
     BuiltValueNullFieldError.checkNotNull(definition, r'TypeDef', 'definition');
     BuiltValueNullFieldError.checkNotNull(
         annotations, r'TypeDef', 'annotations');
     BuiltValueNullFieldError.checkNotNull(docs, r'TypeDef', 'docs');
+    BuiltValueNullFieldError.checkNotNull(types, r'TypeDef', 'types');
   }
 
   @override
@@ -46,15 +50,20 @@
         name == other.name &&
         definition == other.definition &&
         annotations == other.annotations &&
-        docs == other.docs;
+        docs == other.docs &&
+        types == other.types;
   }
 
   @override
   int get hashCode {
-    return $jf($jc(
-        $jc($jc($jc(0, name.hashCode), definition.hashCode),
-            annotations.hashCode),
-        docs.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, name.hashCode);
+    _$hash = $jc(_$hash, definition.hashCode);
+    _$hash = $jc(_$hash, annotations.hashCode);
+    _$hash = $jc(_$hash, docs.hashCode);
+    _$hash = $jc(_$hash, types.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -63,7 +72,8 @@
           ..add('name', name)
           ..add('definition', definition)
           ..add('annotations', annotations)
-          ..add('docs', docs))
+          ..add('docs', docs)
+          ..add('types', types))
         .toString();
   }
 }
@@ -119,6 +129,18 @@
     super.docs = docs;
   }
 
+  @override
+  ListBuilder<Reference> get types {
+    _$this;
+    return super.types;
+  }
+
+  @override
+  set types(ListBuilder<Reference> types) {
+    _$this;
+    super.types = types;
+  }
+
   _$TypeDefBuilder() : super._();
 
   TypeDefBuilder get _$this {
@@ -128,6 +150,7 @@
       super.definition = $v.definition;
       super.annotations = $v.annotations.toBuilder();
       super.docs = $v.docs.toBuilder();
+      super.types = $v.types.toBuilder();
       _$v = null;
     }
     return this;
@@ -157,7 +180,8 @@
               definition: BuiltValueNullFieldError.checkNotNull(
                   definition, r'TypeDef', 'definition'),
               annotations: annotations.build(),
-              docs: docs.build());
+              docs: docs.build(),
+              types: types.build());
     } catch (_) {
       late String _$failedField;
       try {
@@ -165,6 +189,8 @@
         annotations.build();
         _$failedField = 'docs';
         docs.build();
+        _$failedField = 'types';
+        types.build();
       } catch (e) {
         throw new BuiltValueNestedFieldError(
             r'TypeDef', _$failedField, e.toString());
@@ -176,4 +202,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/code_builder/lib/src/visitors.dart b/code_builder/lib/src/visitors.dart
index 5defdbd..0075bd7 100644
--- a/code_builder/lib/src/visitors.dart
+++ b/code_builder/lib/src/visitors.dart
@@ -17,6 +17,7 @@
 import 'specs/mixin.dart';
 import 'specs/reference.dart';
 import 'specs/type_function.dart';
+import 'specs/type_record.dart';
 import 'specs/type_reference.dart';
 import 'specs/typedef.dart';
 
@@ -48,6 +49,8 @@
 
   T visitMethod(Method spec, [T? context]);
 
+  T visitRecordType(RecordType spec, [T? context]);
+
   T visitReference(Reference spec, [T? context]);
 
   T visitSpec(Spec spec, [T? context]);
diff --git a/code_builder/pubspec.yaml b/code_builder/pubspec.yaml
index f3cce8f..c199431 100644
--- a/code_builder/pubspec.yaml
+++ b/code_builder/pubspec.yaml
@@ -1,11 +1,11 @@
 name: code_builder
-version: 4.4.0
+version: 4.5.0
 description: >-
   A fluent, builder-based library for generating valid Dart code
 repository: https://github.com/dart-lang/code_builder
 
 environment:
-  sdk: '>=2.17.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
   built_collection: ^5.0.0
@@ -18,7 +18,7 @@
   build: ^2.0.0
   build_runner: ^2.0.3
   built_value_generator: ^8.0.0
+  dart_flutter_team_lints: ^1.0.0
   dart_style: ^2.0.0
-  lints: ^2.0.0
   source_gen: ^1.0.0
   test: ^1.16.0
diff --git a/dart_style/BUILD.gn b/dart_style/BUILD.gn
index 84a3b4f..9aff74f 100644
--- a/dart_style/BUILD.gn
+++ b/dart_style/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for dart_style-2.2.4
+# This file is generated by package_importer.py for dart_style-2.3.1
 
 import("//build/dart/dart_library.gni")
 
 dart_library("dart_style") {
   package_name = "dart_style"
 
-  language_version = "2.17"
+  language_version = "2.19"
 
   disable_analysis = true
 
@@ -41,6 +41,7 @@
     "src/line_splitting/solve_state.dart",
     "src/line_splitting/solve_state_queue.dart",
     "src/line_writer.dart",
+    "src/marking_scheme.dart",
     "src/nesting_builder.dart",
     "src/nesting_level.dart",
     "src/rule/argument.dart",
diff --git a/dart_style/CHANGELOG.md b/dart_style/CHANGELOG.md
index 3e75c35..f2bb903 100644
--- a/dart_style/CHANGELOG.md
+++ b/dart_style/CHANGELOG.md
@@ -1,3 +1,46 @@
+# 2.3.1
+
+* Hide `--fix` and related options in `--help`. The options are still there and
+  supported, but are no longer shown by default. Eventually, we would like all
+  users to move to using `dart fix` instead of `dart format --fix`.
+* Don't indent `||` pattern operands in switch expression cases.  
+* Don't format `sealed`, `interface`, and `final` keywords on mixin
+  declarations. The proposal was updated to no longer support them.
+* Don't split before a single-section cascade following a record literal.
+* Give records block-like formatting in argument lists (#1205).
+
+# 2.3.0
+
+## New language features
+
+* Format patterns and related features.
+* Format record expressions and record type annotations.
+* Format class modifiers `base`, `final`, `interface`, `mixin`, and `sealed`.
+* Format `inline class` declarations.
+* Format unnamed libraries.
+
+## Bug fixes and style changes
+
+* Handle `sync*` and `async*` functions with `=>` bodies.
+* Fix bug where parameter metadata wouldn't always split when it should.
+* Don't split after `<` in collection literals.
+* Better indentation of multiline function types inside type argument lists. 
+
+## Internal changes
+
+* Use typed `_visitFunctionOrMethodDeclaration` instead of dynamically typed.
+* Fix metadata test to not fail when record syntax makes whitespace between
+  metadata annotation names and `(` significant ([sdk#50769][]).
+* Require Dart 2.19.
+* Require `package:analyzer` `^5.7.0`.
+
+[sdk#50769]: https://github.com/dart-lang/sdk/issues/50769
+
+# 2.2.5
+
+* Format unnamed libraries.
+* Require Dart 2.17.
+
 # 2.2.4
 
 * Unify how brace-delimited syntax is formatted. This is mostly an internal
@@ -16,7 +59,7 @@
 * Don't allow a line comment in an argument list to cause preceding arguments
   to be misformatted.
 * Remove blank lines after a line comment at the end of a body.
-* Require `package:analyzer` `>=4.4.0 <6.0.0`. 
+* Require `package:analyzer` `>=4.4.0 <6.0.0`.
 
 # 2.2.3
 
diff --git a/dart_style/README.md b/dart_style/README.md
index e1c3f4d..dbab7cc 100644
--- a/dart_style/README.md
+++ b/dart_style/README.md
@@ -93,6 +93,9 @@
 
     $ dart format --output=none --set-exit-if-changed .
 
+[presubmit script]: https://www.chromium.org/developers/how-tos/depottools/presubmit-scripts
+[commit hook]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
+
 ## Running other versions of the formatter CLI command
 
 If you need to run a different version of the formatter, you can
diff --git a/dart_style/benchmark/after.dart.txt b/dart_style/benchmark/after.dart.txt
index 30a35b1..2d2ec6b 100644
--- a/dart_style/benchmark/after.dart.txt
+++ b/dart_style/benchmark/after.dart.txt
@@ -171,6 +171,114 @@
       : type = type,
         sources = sources,
         cache = new PubspecCache(type, sources) {
+    // A fairly large switch statement.
+    switch (region) {
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+    }
+
     for (var package in useLatest) {
       _forceLatest.add(package);
     }
diff --git a/dart_style/benchmark/before.dart.txt b/dart_style/benchmark/before.dart.txt
index b0fb12e..9f9867c 100644
--- a/dart_style/benchmark/before.dart.txt
+++ b/dart_style/benchmark/before.dart.txt
@@ -171,6 +171,114 @@
       : type = type,
         sources = sources,
         cache = new PubspecCache(type, sources) {
+    // A fairly large switch statement.
+    switch (region) {
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.everywhere:
+        return 0.45;
+      case Region.n:
+        return lerpDouble(pos.y, 0, height, min, max);
+      case Region.ne:
+        var distance = math.max(width - pos.x - 1, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.e:
+        return lerpDouble(pos.x, 0, width, min, max);
+      case Region.se:
+        var distance = math.max(width - pos.x - 1, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.s:
+        return lerpDouble(pos.y, 0, height, max, min);
+      case Region.sw:
+        var distance = math.max(pos.x, height - pos.y - 1);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+      case Region.w:
+        return lerpDouble(pos.x, 0, width, max, min);
+      case Region.nw:
+        var distance = math.max(pos.x, pos.y);
+        var range = math.min(width, height);
+        return lerpDouble(distance, 0, range, min, max);
+    }
+
     for (var package in useLatest) {
       _forceLatest.add(package);
     }
diff --git a/dart_style/lib/src/argument_list_visitor.dart b/dart_style/lib/src/argument_list_visitor.dart
index b02b9ef..1774faf 100644
--- a/dart_style/lib/src/argument_list_visitor.dart
+++ b/dart_style/lib/src/argument_list_visitor.dart
@@ -453,13 +453,18 @@
 
       // Tell it to use the rule we've already created.
       visitor.beforeBlock(argumentBlock, blockRule, previousSplit);
-    } else if (_allArguments.length > 1) {
+    } else if (_allArguments.length > 1 ||
+        _allArguments.first is RecordLiteral) {
       // Edge case: Only bump the nesting if there are multiple arguments. This
       // lets us avoid spurious indentation in cases like:
       //
       //     function(function(() {
       //       body;
       //     }));
+      //
+      // Do bump the nesting if the single argument is a record because records
+      // are formatted like regular values when they appear in argument lists
+      // even though they internally get block-like formatting.
       visitor.builder.startBlockArgumentNesting();
     } else if (argument is! NamedExpression) {
       // Edge case: Likewise, don't force the argument to split if there is
@@ -471,14 +476,16 @@
     }
 
     if (argument is NamedExpression) {
-      visitor.visitNamedArgument(argument, rule as NamedRule);
+      visitor.visitNamedNode(argument.name.label.token, argument.name.colon,
+          argument.expression, rule as NamedRule);
     } else {
       visitor.visit(argument);
     }
 
     if (argumentBlock != null) {
       rule.enableSplitOnInnerRules();
-    } else if (_allArguments.length > 1) {
+    } else if (_allArguments.length > 1 ||
+        _allArguments.first is RecordLiteral) {
       visitor.builder.endBlockArgumentNesting();
     } else if (argument is! NamedExpression) {
       rule.enableSplitOnInnerRules();
@@ -534,6 +541,7 @@
     // TODO(rnystrom): Should we step into parenthesized expressions?
 
     if (expression is ListLiteral) return expression.leftBracket;
+    if (expression is RecordLiteral) return expression.leftParenthesis;
     if (expression is SetOrMapLiteral) return expression.leftBracket;
     if (expression is SingleStringLiteral && expression.isMultiline) {
       return expression.beginToken;
diff --git a/dart_style/lib/src/ast_extensions.dart b/dart_style/lib/src/ast_extensions.dart
index 3d63c9e..9f97676 100644
--- a/dart_style/lib/src/ast_extensions.dart
+++ b/dart_style/lib/src/ast_extensions.dart
@@ -88,40 +88,6 @@
 }
 
 extension ExpressionExtensions on Expression {
-  /// Whether [expression] is a collection literal, or a call with a trailing
-  /// comma in an argument list.
-  ///
-  /// In that case, when the expression is a target of a cascade, we don't
-  /// force a split before the ".." as eagerly to avoid ugly results like:
-  ///
-  ///     [
-  ///       1,
-  ///       2,
-  ///     ]..addAll(numbers);
-  bool get isCollectionLike {
-    var expression = this;
-    if (expression is ListLiteral) return false;
-    if (expression is SetOrMapLiteral) return false;
-
-    // If the target is a call with a trailing comma in the argument list,
-    // treat it like a collection literal.
-    ArgumentList? arguments;
-    if (expression is InvocationExpression) {
-      arguments = expression.argumentList;
-    } else if (expression is InstanceCreationExpression) {
-      arguments = expression.argumentList;
-    }
-
-    // TODO(rnystrom): Do we want to allow an invocation where the last
-    // argument is a collection literal? Like:
-    //
-    //     foo(argument, [
-    //       element
-    //     ])..cascade();
-
-    return arguments == null || !arguments.arguments.hasCommaAfter;
-  }
-
   /// Whether this is an argument in an argument list with a trailing comma.
   bool get isTrailingCommaArgument {
     var parent = this.parent;
diff --git a/dart_style/lib/src/chunk.dart b/dart_style/lib/src/chunk.dart
index 43e3d84..d271cf6 100644
--- a/dart_style/lib/src/chunk.dart
+++ b/dart_style/lib/src/chunk.dart
@@ -2,6 +2,7 @@
 // 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 'fast_hash.dart';
+import 'marking_scheme.dart';
 import 'nesting_level.dart';
 import 'rule/rule.dart';
 
@@ -114,10 +115,8 @@
 
   /// Whether this chunk marks the end of a range of chunks that can be line
   /// split independently of the following chunks.
-  ///
-  /// You must call markDivide() before accessing this.
   bool get canDivide => _canDivide;
-  late final bool _canDivide;
+  bool _canDivide = true;
 
   /// The number of characters in this chunk when unsplit.
   int get length => (_spaceWhenUnsplit ? 1 : 0) + _text.length;
@@ -177,9 +176,12 @@
   /// that [Rule].
   bool indentBlock(int Function(Rule) getValue) => false;
 
-  // Mark whether this chunk can divide the range of chunks.
-  void markDivide(bool canDivide) {
-    _canDivide = canDivide;
+  /// Prevent the line splitter from diving at this chunk.
+  ///
+  /// This should be called on any chunk where line splitting choices before
+  /// and after this chunk relate to each other.
+  void preventDivide() {
+    _canDivide = false;
   }
 
   @override
@@ -293,7 +295,11 @@
 /// This is a wrapper around the cost so that spans have unique identities.
 /// This way we can correctly avoid paying the cost multiple times if the same
 /// span is split by multiple chunks.
-class Span extends FastHash {
+///
+/// Spans can be marked during processing in an algorithm but should be left
+/// unmarked when the algorithm finishes to make marking work in subsequent
+/// calls.
+class Span extends FastHash with Markable {
   /// The cost applied when the span is split across multiple lines or `null`
   /// if the span is for a multisplit.
   final int cost;
diff --git a/dart_style/lib/src/chunk_builder.dart b/dart_style/lib/src/chunk_builder.dart
index 5b921fa..480bc79 100644
--- a/dart_style/lib/src/chunk_builder.dart
+++ b/dart_style/lib/src/chunk_builder.dart
@@ -60,6 +60,23 @@
   /// Whether the next chunk should be flush left.
   bool _pendingFlushLeft = false;
 
+  /// Whether subsequent hard splits should be allowed to divide for line
+  /// splitting.
+  ///
+  /// Most rules used by multiple chunks will never have a hard split on a
+  /// chunk between two chunks using that rule. That means when we see a hard
+  /// split, we can line split the chunks before and after it independently.
+  ///
+  /// However, there are a couple of places where a single rule spans
+  /// multiple chunks where hard splits also appear interleaved between them.
+  /// Currently, that's the rule for splitting switch case bodies, and the
+  /// rule for parameter list metadata.
+  ///
+  /// In those circumstances, we mark the chunk with the hard split as not
+  /// being allowed to divide. That way, all of the chunks using the rule are
+  /// split together.
+  bool _pendingPreventDivide = false;
+
   /// Whether the most recently written output was a comment.
   bool _afterComment = false;
 
@@ -156,6 +173,7 @@
 
     _nesting.commitNesting();
     _afterComment = false;
+    _pendingPreventDivide = false;
   }
 
   /// Writes one or two hard newlines.
@@ -168,10 +186,14 @@
   /// nesting. If [nest] is `true` then the next line will use expression
   /// nesting.
   void writeNewline(
-      {bool isDouble = false, bool flushLeft = false, bool nest = false}) {
+      {bool isDouble = false,
+      bool flushLeft = false,
+      bool nest = false,
+      bool preventDivide = false}) {
     _pendingNewlines = isDouble ? 2 : 1;
     _pendingFlushLeft = flushLeft;
     _pendingNested = nest;
+    _pendingPreventDivide |= preventDivide;
   }
 
   /// Writes a space before the subsequent non-whitespace text.
@@ -855,10 +877,14 @@
           isHard: isHard, isDouble: isDouble, space: space);
     }
 
+    if (chunk.rule.isHardened) {
+      _handleHardSplit();
+
+      if (_pendingPreventDivide) chunk.preventDivide();
+    }
+
     _pendingNewlines = 0;
     _pendingNested = false;
-
-    if (chunk.rule.isHardened) _handleHardSplit();
     return chunk;
   }
 
@@ -891,23 +917,6 @@
     return chunk;
   }
 
-  /// Returns true if we can divide the chunks at [index] and line split the
-  /// ones before and after that separately.
-  bool _canDivideAt(int i) {
-    // Don't divide at the first chunk.
-    if (i == 0) return false;
-
-    var chunk = _chunks[i];
-    if (!chunk.rule.isHardened) return false;
-    if (chunk.nesting.isNested) return false;
-
-    // If the chunk is the ending delimiter of a block, then don't separate it
-    // and its children from the preceding beginning of the block.
-    if (_chunks[i] is BlockChunk) return false;
-
-    return true;
-  }
-
   /// Pre-processes the chunks after they are done being written by the visitor
   /// but before they are run through the line splitter.
   ///
@@ -918,13 +927,31 @@
     // splits, along with all of the other rules they constrain.
     _hardenRules();
 
-    // Now that we know where all of the divided chunk sections are, mark the
-    // chunks.
     for (var i = 0; i < _chunks.length; i++) {
-      _chunks[i].markDivide(_canDivideAt(i));
+      var chunk = _chunks[i];
+      if (!_canDivide(chunk)) chunk.preventDivide();
     }
   }
 
+  /// Returns true if we can divide the chunks at [index] and line split the
+  /// ones before and after that separately.
+  bool _canDivide(Chunk chunk) {
+    // Don't divide at the first chunk.
+    if (chunk == _chunks.first) return false;
+
+    // Can't divide soft rules.
+    if (!chunk.rule.isHardened) return false;
+
+    // Can't divide in the middle of expression nesting.
+    if (chunk.nesting.isNested) return false;
+
+    // If the chunk is the ending delimiter of a block, then don't separate it
+    // and its children from the preceding beginning of the block.
+    if (chunk is BlockChunk) return false;
+
+    return true;
+  }
+
   /// Hardens the active rules when a hard split occurs within them.
   void _handleHardSplit() {
     if (_rules.isEmpty) return;
diff --git a/dart_style/lib/src/cli/formatter_options.dart b/dart_style/lib/src/cli/formatter_options.dart
index c2f185b..6f4e6e7 100644
--- a/dart_style/lib/src/cli/formatter_options.dart
+++ b/dart_style/lib/src/cli/formatter_options.dart
@@ -11,7 +11,7 @@
 import 'summary.dart';
 
 // Note: The following line of code is modified by tool/grind.dart.
-const dartStyleVersion = '2.2.4';
+const dartStyleVersion = '2.3.1';
 
 /// Global options that affect how the formatter produces and uses its outputs.
 class FormatterOptions {
diff --git a/dart_style/lib/src/cli/options.dart b/dart_style/lib/src/cli/options.dart
index aea33ed..3f4f48f 100644
--- a/dart_style/lib/src/cli/options.dart
+++ b/dart_style/lib/src/cli/options.dart
@@ -74,10 +74,10 @@
       help: 'Return exit code 1 if there are any formatting changes.');
 
   if (verbose) parser.addSeparator('Non-whitespace fixes (off by default):');
-  parser.addFlag('fix', negatable: false, help: 'Apply all style fixes.');
+  parser.addFlag('fix',
+      negatable: false, help: 'Apply all style fixes.', hide: !verbose);
 
   for (var fix in StyleFix.all) {
-    // TODO(rnystrom): Allow negating this if used in concert with "--fix"?
     parser.addFlag('fix-${fix.name}',
         negatable: false, help: fix.description, hide: !verbose);
   }
@@ -85,7 +85,10 @@
   if (verbose) parser.addSeparator('Other options:');
 
   parser.addOption('line-length',
-      abbr: 'l', help: 'Wrap lines longer than this.', defaultsTo: '80');
+      abbr: 'l',
+      help: 'Wrap lines longer than this.',
+      defaultsTo: '80',
+      hide: true);
   parser.addOption('indent',
       abbr: 'i',
       help: 'Add this many spaces of leading indentation.',
diff --git a/dart_style/lib/src/cli/show.dart b/dart_style/lib/src/cli/show.dart
index 3343433..eea28e1 100644
--- a/dart_style/lib/src/cli/show.dart
+++ b/dart_style/lib/src/cli/show.dart
@@ -71,7 +71,7 @@
   /// Describes the directory whose contents are about to be processed.
   void directory(String path) {
     if (this == Show.legacy || this == Show.overwrite) {
-      print('Formatting directory $directory:');
+      print('Formatting directory $path:');
     }
   }
 
diff --git a/dart_style/lib/src/dart_formatter.dart b/dart_style/lib/src/dart_formatter.dart
index db363c4..30ea106 100644
--- a/dart_style/lib/src/dart_formatter.dart
+++ b/dart_style/lib/src/dart_formatter.dart
@@ -4,6 +4,7 @@
 import 'dart:math' as math;
 
 import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/utilities.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
@@ -83,22 +84,11 @@
   /// Returns a new [SourceCode] containing the formatted code and the resulting
   /// selection, if any.
   SourceCode formatSource(SourceCode source) {
-    // Enable all features that are enabled by default in the current analyzer
-    // version.
-    // TODO(paulberry): consider plumbing in experiment enable flags from the
-    // command line.
-    var featureSet = FeatureSet.fromEnableFlags2(
-      sdkLanguageVersion: Version(2, 17, 0),
-      flags: [
-        'enhanced-enums',
-        'named-arguments-anywhere',
-        'super-parameters',
-      ],
-    );
-
     var inputOffset = 0;
     var text = source.text;
     var unitSourceCode = source;
+
+    // If we're parsing a single statement, wrap the source in a fake function.
     if (!source.isCompilationUnit) {
       var prefix = 'void foo() { ';
       inputOffset = prefix.length;
@@ -115,12 +105,19 @@
     }
 
     // Parse it.
-    var parseResult = parseString(
-      content: text,
-      featureSet: featureSet,
-      path: source.uri,
-      throwIfDiagnostics: false,
-    );
+    var parseResult = _parse(text, source.uri, patterns: true);
+
+    // If we couldn't parse it with patterns enabled, it may be because of
+    // one of the breaking syntax changes to switch cases. Try parsing it
+    // again without patterns.
+    if (parseResult.errors.isNotEmpty) {
+      var withoutPatternsResult = _parse(text, source.uri, patterns: false);
+
+      // If we succeeded this time, use this parse instead.
+      if (withoutPatternsResult.errors.isEmpty) {
+        parseResult = withoutPatternsResult;
+      }
+    }
 
     // Infer the line ending if not given one. Do it here since now we know
     // where the lines start.
@@ -180,4 +177,49 @@
 
     return output;
   }
+
+  /// Parse [source] from [uri].
+  ///
+  /// If [patterns] is `true`, the parse at the latest language version
+  /// which supports patterns and treats switch cases as patterns. If `false`,
+  /// then parses using an older language version where switch cases are
+  /// constant expressions.
+  ///
+  // TODO(rnystrom): This is a pretty big hack. Up until now, every language
+  // version was a strict syntactic superset of all previous versions. That let
+  // the formatter parse every file at the latest language version without
+  // having to detect each file's actual version, which requires digging around
+  // in the file system for package configs and looking for "@dart" comments in
+  // files. It also means the library API that parses arbitrary strings doesn't
+  // have to worry about what version the code should be interpreted as.
+  //
+  // But with patterns, a small number of switch cases are no longer
+  // syntactically valid. Breakage from this is very rare. Instead of adding
+  // the machinery to detect language versions (which is likely to be slow and
+  // brittle), we just try parsing everything with patterns enabled. When a
+  // parse error occurs, we try parsing it again with pattern disabled. If that
+  // happens to parse without error, then we use that result instead.
+  ParseStringResult _parse(String source, String? uri,
+      {required bool patterns}) {
+    // Enable all features that are enabled by default in the current analyzer
+    // version.
+    var featureSet = FeatureSet.fromEnableFlags2(
+      sdkLanguageVersion: Version(2, 19, 0),
+      flags: [
+        'inline-class',
+        'class-modifiers',
+        if (patterns) 'patterns',
+        'records',
+        'sealed-class',
+        'unnamed-libraries',
+      ],
+    );
+
+    return parseString(
+      content: source,
+      featureSet: featureSet,
+      path: uri,
+      throwIfDiagnostics: false,
+    );
+  }
 }
diff --git a/dart_style/lib/src/debug.dart b/dart_style/lib/src/debug.dart
index 351e80e..ea15124 100644
--- a/dart_style/lib/src/debug.dart
+++ b/dart_style/lib/src/debug.dart
@@ -76,9 +76,6 @@
 
   addSpans(chunks);
 
-  var spans = spanSet.toList();
-  var rules = chunks.map((chunk) => chunk.rule).toSet();
-
   var rows = <List<String>>[];
 
   void addChunk(List<Chunk> chunks, String prefix, int index) {
@@ -108,6 +105,7 @@
     if (rule.isHardened) ruleString += '!';
     row.add(ruleString);
 
+    var rules = chunks.map((chunk) => chunk.rule).toSet();
     var constrainedRules = rule.constrainedRules.toSet().intersection(rules);
     writeIf(
         constrainedRules.isNotEmpty, () => "-> ${constrainedRules.join(" ")}");
@@ -123,30 +121,36 @@
     writeIf(chunk.indent != 0, () => 'indent ${chunk.indent}');
     writeIf(chunk.nesting.indent != 0, () => 'nest ${chunk.nesting}');
 
+    var spans = spanSet.toList();
     if (spans.length <= 20) {
       var spanBars = '';
       for (var span in spans) {
         if (chunk.spans.contains(span)) {
-          if (index == 0 || !chunks[index - 1].spans.contains(span)) {
+          if (index == chunks.length - 1 ||
+              !chunks[index + 1].spans.contains(span)) {
+            // This is the last chunk with the span.
+            spanBars += 'â•™';
+          } else {
+            spanBars += 'â•‘';
+          }
+        } else {
+          // If the next chunk has this span, then show it bridging this chunk
+          // and the next because a split between them breaks the span.
+          if (index < chunks.length - 1 &&
+              chunks[index + 1].spans.contains(span)) {
             if (span.cost == 1) {
               spanBars += 'â•“';
             } else {
               spanBars += span.cost.toString();
             }
-          } else {
-            spanBars += 'â•‘';
-          }
-        } else {
-          if (index > 0 && chunks[index - 1].spans.contains(span)) {
-            spanBars += 'â•™';
-          } else {
-            spanBars += ' ';
           }
         }
       }
       row.add(spanBars);
     }
 
+    row.add(chunk.spans.map((span) => span.id).join(' '));
+
     if (chunk.text.length > 70) {
       row.add(chunk.text.substring(0, 70));
     } else {
diff --git a/dart_style/lib/src/line_splitting/solve_state.dart b/dart_style/lib/src/line_splitting/solve_state.dart
index 90abec7..4aa2efd 100644
--- a/dart_style/lib/src/line_splitting/solve_state.dart
+++ b/dart_style/lib/src/line_splitting/solve_state.dart
@@ -272,17 +272,23 @@
   void _calculateSplits() {
     // Figure out which expression nesting levels got split and need to be
     // assigned columns.
-    var usedNestingLevels = <NestingLevel>{};
+    var usedNestingLevels = <NestingLevel>[];
     for (var i = 0; i < _splitter.chunks.length; i++) {
       var chunk = _splitter.chunks[i];
       if (chunk.rule.isSplit(getValue(chunk.rule), chunk)) {
-        usedNestingLevels.add(chunk.nesting);
-        chunk.nesting.clearTotalUsedIndent();
+        var nesting = chunk.nesting;
+        if (nesting.mark()) {
+          usedNestingLevels.add(nesting);
+          nesting.clearTotalUsedIndent();
+        }
       }
     }
 
     for (var nesting in usedNestingLevels) {
-      nesting.refreshTotalUsedIndent(usedNestingLevels);
+      nesting.refreshTotalUsedIndent();
+    }
+    for (var nesting in usedNestingLevels) {
+      nesting.unmark();
     }
 
     _splits = SplitSet(_splitter.chunks.length);
@@ -340,10 +346,11 @@
       start = end;
     }
 
-    // The set of spans that contain chunks that ended up splitting. We store
-    // these in a set so a span's cost doesn't get double-counted if more than
-    // one split occurs in it.
-    var splitSpans = <Span>{};
+    // The list of spans that contain chunks that ended up splitting. These are
+    // made unique by marking the spans during the run, adding them to this list
+    // to be able to unmark them again. We have to keep track of uniqueness to
+    // avoid double-counting if more than one split occurs in it.
+    var splitSpans = <Span>[];
 
     // The nesting level of the chunk that ended the previous line.
     NestingLevel? previousNesting;
@@ -354,7 +361,12 @@
       if (_splits.shouldSplitAt(i)) {
         endLine(i);
 
-        splitSpans.addAll(chunk.spans);
+        for (var span in chunk.spans) {
+          if (span.mark()) {
+            splitSpans.add(span);
+            cost += span.cost;
+          }
+        }
 
         // Do not allow sequential lines to have the same indentation but for
         // different reasons. In other words, don't allow different expressions
@@ -408,9 +420,9 @@
       if (value != Rule.unsplit) cost += rule.cost;
     });
 
-    // Add the costs for the spans containing splits.
+    // Unmark spans again so they're ready for another run.
     for (var span in splitSpans) {
-      cost += span.cost;
+      span.unmark();
     }
 
     // Finish the last line.
diff --git a/dart_style/lib/src/line_splitting/solve_state_queue.dart b/dart_style/lib/src/line_splitting/solve_state_queue.dart
index f1622f6..5ce2975 100644
--- a/dart_style/lib/src/line_splitting/solve_state_queue.dart
+++ b/dart_style/lib/src/line_splitting/solve_state_queue.dart
@@ -88,8 +88,11 @@
 
   /// Compares the overflow and cost of [a] to [b].
   int _compareScore(SolveState a, SolveState b) {
-    if (a.splits.cost != b.splits.cost) {
-      return a.splits.cost.compareTo(b.splits.cost);
+    var aCost = a.splits.cost;
+    var bCost = b.splits.cost;
+    if (aCost != bCost) {
+      if (aCost < bCost) return -1;
+      return 1;
     }
 
     return a.overflowChars.compareTo(b.overflowChars);
diff --git a/dart_style/lib/src/line_writer.dart b/dart_style/lib/src/line_writer.dart
index ae5866c..8992c43 100644
--- a/dart_style/lib/src/line_writer.dart
+++ b/dart_style/lib/src/line_writer.dart
@@ -180,10 +180,11 @@
   void _writeChunksUnsplit(BlockChunk block) {
     for (var chunk in block.children) {
       if (chunk.spaceWhenUnsplit) _buffer.write(' ');
-      _writeChunk(chunk);
 
       // Recurse into the block.
       if (chunk is BlockChunk) _writeChunksUnsplit(chunk);
+
+      _writeChunk(chunk);
     }
   }
 
diff --git a/dart_style/lib/src/marking_scheme.dart b/dart_style/lib/src/marking_scheme.dart
new file mode 100644
index 0000000..ff94de3
--- /dev/null
+++ b/dart_style/lib/src/marking_scheme.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, 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.
+
+/// A mixin for marking classes.
+mixin Markable {
+  bool _isMarked = false;
+
+  bool mark() {
+    if (_isMarked) return false;
+    _isMarked = true;
+    return true;
+  }
+
+  bool get isMarked => _isMarked;
+
+  void unmark() {
+    _isMarked = false;
+  }
+}
diff --git a/dart_style/lib/src/nesting_builder.dart b/dart_style/lib/src/nesting_builder.dart
index 9204a0d..e9d7cc8 100644
--- a/dart_style/lib/src/nesting_builder.dart
+++ b/dart_style/lib/src/nesting_builder.dart
@@ -80,10 +80,10 @@
     assert(_pendingNesting == null);
     assert(_nesting.indent == 0);
 
+    _stack.removeLast();
+
     // If this fails, an unindent() call did not have a preceding indent() call.
     assert(_stack.isNotEmpty);
-
-    _stack.removeLast();
   }
 
   /// Begins a new expression nesting level [indent] deeper than the current
diff --git a/dart_style/lib/src/nesting_level.dart b/dart_style/lib/src/nesting_level.dart
index ba029a3..65306d5 100644
--- a/dart_style/lib/src/nesting_level.dart
+++ b/dart_style/lib/src/nesting_level.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'fast_hash.dart';
+import 'marking_scheme.dart';
 
 /// A single level of expression nesting.
 ///
@@ -19,7 +20,11 @@
 /// indented relative to the outer expression. It's almost always
 /// [Indent.expression], but cascades are special magic snowflakes and use
 /// [Indent.cascade].
-class NestingLevel extends FastHash {
+///
+/// NestingLEvels can be marked during processing in an algorithm but should be
+/// left unmarked when the algorithm finishes to make marking work in subsequent
+/// calls.
+class NestingLevel extends FastHash with Markable {
   /// The nesting level surrounding this one, or `null` if this is represents
   /// top level code in a block.
   final NestingLevel? parent;
@@ -56,19 +61,19 @@
   }
 
   /// Calculates the total amount of indentation from this nesting level and
-  /// all of its parents assuming only [usedNesting] levels are in use.
-  void refreshTotalUsedIndent(Set<NestingLevel> usedNesting) {
+  /// all of its parents assuming only marked levels are in use.
+  void refreshTotalUsedIndent() {
     var totalIndent = _totalUsedIndent;
     if (totalIndent != null) return;
 
     totalIndent = 0;
 
     if (parent != null) {
-      parent!.refreshTotalUsedIndent(usedNesting);
+      parent!.refreshTotalUsedIndent();
       totalIndent += parent!.totalUsedIndent;
     }
 
-    if (usedNesting.contains(this)) totalIndent += indent;
+    if (isMarked) totalIndent += indent;
 
     _totalUsedIndent = totalIndent;
   }
diff --git a/dart_style/lib/src/rule/argument.dart b/dart_style/lib/src/rule/argument.dart
index e99504b..0aa1648 100644
--- a/dart_style/lib/src/rule/argument.dart
+++ b/dart_style/lib/src/rule/argument.dart
@@ -5,37 +5,15 @@
 import 'rule.dart';
 
 /// Base class for a rule that handles argument or parameter lists.
-abstract class ArgumentRule extends Rule {
+abstract class ArgumentRule extends SplitContainingRule {
   /// The chunks prior to each positional argument.
   final List<Chunk?> _arguments = [];
 
-  /// If true, then inner rules that are written will force this rule to split.
-  ///
-  /// Temporarily disabled while writing collection arguments so that they can
-  /// be multi-line without forcing the whole argument list to split.
-  bool _trackInnerRules = true;
-
-  /// Don't split when an inner collection rule splits.
-  @override
-  bool get splitsOnInnerRules => _trackInnerRules;
-
   /// Remembers [chunk] as containing the split that occurs right before an
   /// argument in the list.
   void beforeArgument(Chunk? chunk) {
     _arguments.add(chunk);
   }
-
-  /// Disables tracking inner rules while a collection argument is written.
-  void disableSplitOnInnerRules() {
-    assert(_trackInnerRules == true);
-    _trackInnerRules = false;
-  }
-
-  /// Re-enables tracking inner rules.
-  void enableSplitOnInnerRules() {
-    assert(_trackInnerRules == false);
-    _trackInnerRules = true;
-  }
 }
 
 /// Rule for handling positional argument lists.
diff --git a/dart_style/lib/src/rule/rule.dart b/dart_style/lib/src/rule/rule.dart
index 2846afc..e396b5a 100644
--- a/dart_style/lib/src/rule/rule.dart
+++ b/dart_style/lib/src/rule/rule.dart
@@ -213,6 +213,31 @@
   String toString() => '$id';
 }
 
+/// A rule that may contain splits which don't force it to split.
+class SplitContainingRule extends Rule {
+  /// If true, then inner rules that are written will force this rule to split.
+  ///
+  /// Temporarily disabled while writing collection arguments so that they can
+  /// be multi-line without forcing the whole argument list to split.
+  bool _trackInnerRules = true;
+
+  /// Don't split when an inner collection rule splits.
+  @override
+  bool get splitsOnInnerRules => _trackInnerRules;
+
+  /// Disables tracking inner rules while a collection argument is written.
+  void disableSplitOnInnerRules() {
+    assert(_trackInnerRules == true);
+    _trackInnerRules = false;
+  }
+
+  /// Re-enables tracking inner rules.
+  void enableSplitOnInnerRules() {
+    assert(_trackInnerRules == false);
+    _trackInnerRules = true;
+  }
+}
+
 /// Describes a value constraint that one [Rule] places on another rule's
 /// values.
 ///
diff --git a/dart_style/lib/src/source_visitor.dart b/dart_style/lib/src/source_visitor.dart
index 8857412..1937b87 100644
--- a/dart_style/lib/src/source_visitor.dart
+++ b/dart_style/lib/src/source_visitor.dart
@@ -255,7 +255,7 @@
     // and the ")" ends up on its own line.
     if (node.arguments.hasCommaAfter) {
       _visitCollectionLiteral(
-          null, node.leftParenthesis, node.arguments, node.rightParenthesis);
+          node.leftParenthesis, node.arguments, node.rightParenthesis);
       return;
     }
 
@@ -289,7 +289,7 @@
     // and the ")" ends up on its own line.
     if (arguments.hasCommaAfter) {
       _visitCollectionLiteral(
-          null, node.leftParenthesis, arguments, node.rightParenthesis);
+          node.leftParenthesis, arguments, node.rightParenthesis);
       return;
     }
 
@@ -313,7 +313,7 @@
       // and the ")" ends up on its own line.
       if (arguments.hasCommaAfter) {
         _visitCollectionLiteral(
-            null, node.leftParenthesis, arguments, node.rightParenthesis);
+            node.leftParenthesis, arguments, node.rightParenthesis);
         return;
       }
 
@@ -324,6 +324,11 @@
   }
 
   @override
+  void visitAssignedVariablePattern(AssignedVariablePattern node) {
+    token(node.name);
+  }
+
+  @override
   void visitAssignmentExpression(AssignmentExpression node) {
     builder.nestExpression();
 
@@ -342,8 +347,6 @@
 
   @override
   void visitBinaryExpression(BinaryExpression node) {
-    builder.startSpan();
-
     // If a binary operator sequence appears immediately after a `=>`, don't
     // add an extra level of nesting. Instead, let the subsequent operands line
     // up with the first, as in:
@@ -352,43 +355,14 @@
     //         argument &&
     //         argument &&
     //         argument;
-    var isArrowBody = node.parent is ExpressionFunctionBody;
-    if (!isArrowBody) builder.nestExpression();
+    var nest = node.parent is! ExpressionFunctionBody;
 
-    // Start lazily so we don't force the operator to split if a line comment
-    // appears before the first operand.
-    builder.startLazyRule();
-
-    // Flatten out a tree/chain of the same precedence. If we split on this
-    // precedence level, we will break all of them.
-    var precedence = node.operator.type.precedence;
-
-    @override
-    void traverse(Expression e) {
-      if (e is BinaryExpression && e.operator.type.precedence == precedence) {
-        traverse(e.leftOperand);
-
-        space();
-        token(e.operator);
-
-        split();
-        traverse(e.rightOperand);
-      } else {
-        visit(e);
-      }
-    }
-
-    // Blocks as operands to infix operators should always nest like regular
-    // operands. (Granted, this case is exceedingly rare in real code.)
-    builder.startBlockArgumentNesting();
-
-    traverse(node);
-
-    builder.endBlockArgumentNesting();
-
-    if (!isArrowBody) builder.unnest();
-    builder.endSpan();
-    builder.endRule();
+    _visitBinary<BinaryExpression>(
+        node,
+        precedence: node.operator.type.precedence,
+        nest: nest,
+        (expression) => BinaryNode(expression.leftOperand, expression.operator,
+            expression.rightOperand));
   }
 
   @override
@@ -450,9 +424,25 @@
     //       stuff
     //     ]
     //       ..add(more);
-    var splitIfOperandsSplit =
-        node.cascadeSections.length > 1 || node.target.isCollectionLike;
-    if (splitIfOperandsSplit) {
+    var target = node.target;
+    var splitIfTargetSplits = true;
+    if (node.cascadeSections.length > 1) {
+      // Always split if there are multiple cascade sections.
+    } else if (target is ListLiteral ||
+        target is RecordLiteral ||
+        target is SetOrMapLiteral) {
+      splitIfTargetSplits = false;
+    } else if (target is InvocationExpression) {
+      // If the target is a call with a trailing comma in the argument list,
+      // treat it like a collection literal.
+      splitIfTargetSplits = !target.argumentList.arguments.hasCommaAfter;
+    } else if (target is InstanceCreationExpression) {
+      // If the target is a call with a trailing comma in the argument list,
+      // treat it like a collection literal.
+      splitIfTargetSplits = !target.argumentList.arguments.hasCommaAfter;
+    }
+
+    if (splitIfTargetSplits) {
       builder.startLazyRule(node.allowInline ? Rule() : Rule.hard());
     }
 
@@ -463,21 +453,17 @@
 
     // If the cascade section shouldn't cause the cascade to split, end the
     // rule early so it isn't affected by it.
-    if (!splitIfOperandsSplit) {
+    if (!splitIfTargetSplits) {
       builder.startRule(node.allowInline ? Rule() : Rule.hard());
     }
 
     zeroSplit();
 
-    if (!splitIfOperandsSplit) {
-      builder.endRule();
-    }
+    if (!splitIfTargetSplits) builder.endRule();
 
     visitNodes(node.cascadeSections, between: zeroSplit);
 
-    if (splitIfOperandsSplit) {
-      builder.endRule();
-    }
+    if (splitIfTargetSplits) builder.endRule();
 
     builder.endBlockArgumentNesting();
     builder.unnest();
@@ -563,6 +549,19 @@
   }
 
   @override
+  void visitCastPattern(CastPattern node) {
+    builder.startSpan();
+    builder.nestExpression();
+    visit(node.pattern);
+    soloSplit();
+    token(node.asToken);
+    space();
+    visit(node.type);
+    builder.unnest();
+    builder.endSpan();
+  }
+
+  @override
   void visitCatchClause(CatchClause node) {
     token(node.onKeyword, after: space);
     visit(node.exceptionType);
@@ -574,9 +573,9 @@
       token(node.catchKeyword);
       space();
       token(node.leftParenthesis);
-      visit(node.exceptionParameter2);
+      visit(node.exceptionParameter);
       token(node.comma, after: space);
-      visit(node.stackTraceParameter2);
+      visit(node.stackTraceParameter);
       token(node.rightParenthesis);
       space();
     } else {
@@ -596,9 +595,15 @@
 
     builder.nestExpression();
     modifier(node.abstractKeyword);
+    modifier(node.baseKeyword);
+    modifier(node.interfaceKeyword);
+    modifier(node.finalKeyword);
+    modifier(node.sealedKeyword);
+    modifier(node.mixinKeyword);
+    modifier(node.inlineKeyword);
     token(node.classKeyword);
     space();
-    token(node.name2);
+    token(node.name);
     visit(node.typeParameters);
     visit(node.extendsClause);
     _visitClauses(node.withClause, node.implementsClause);
@@ -615,9 +620,14 @@
 
     _simpleStatement(node, () {
       modifier(node.abstractKeyword);
+      modifier(node.baseKeyword);
+      modifier(node.interfaceKeyword);
+      modifier(node.finalKeyword);
+      modifier(node.sealedKeyword);
+      modifier(node.mixinKeyword);
       token(node.typedefKeyword);
       space();
-      token(node.name2);
+      token(node.name);
       visit(node.typeParameters);
       space();
       token(node.equals);
@@ -756,6 +766,12 @@
   }
 
   @override
+  void visitConstantPattern(ConstantPattern node) {
+    token(node.constKeyword, after: space);
+    visit(node.expression);
+  }
+
+  @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
     visitMetadata(node.metadata);
 
@@ -764,7 +780,7 @@
     modifier(node.factoryKeyword);
     visit(node.returnType);
     token(node.period);
-    token(node.name2);
+    token(node.name);
 
     // Make the rule for the ":" span both the preceding parameter list and
     // the entire initialization list. This ensures that we split before the
@@ -906,6 +922,11 @@
   }
 
   @override
+  void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
+    _visitVariablePattern(node.keyword, node.type, node.name);
+  }
+
+  @override
   void visitDefaultFormalParameter(DefaultFormalParameter node) {
     visit(node.parameter);
     if (node.separator != null) {
@@ -981,7 +1002,7 @@
   @override
   void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
     visitMetadata(node.metadata);
-    token(node.name2);
+    token(node.name);
 
     var arguments = node.arguments;
     if (arguments != null) {
@@ -1006,7 +1027,7 @@
     builder.nestExpression();
     token(node.enumKeyword);
     space();
-    token(node.name2);
+    token(node.name);
     visit(node.typeParameters);
     _visitClauses(node.withClause, node.implementsClause);
     space();
@@ -1073,8 +1094,10 @@
     // Space after the parameter list.
     space();
 
-    // The "async" or "sync" keyword.
-    token(node.keyword, after: space);
+    // The "async" or "sync" keyword and "*".
+    token(node.keyword);
+    token(node.star);
+    if (node.keyword != null || node.star != null) space();
 
     // Try to keep the "(...) => " with the start of the body for anonymous
     // functions.
@@ -1250,10 +1273,7 @@
 
     // Don't put a space after `extension` if the extension is unnamed. That
     // way, generic unnamed extensions format like `extension<T> on ...`.
-    if (node.name2 != null) {
-      space();
-      token(node.name2);
-    }
+    token(node.name, before: space);
 
     visit(node.typeParameters);
     soloSplit();
@@ -1283,7 +1303,8 @@
     visitParameterMetadata(node.metadata, () {
       _beginFormalParameter(node);
       token(node.keyword, after: space);
-      visit(node.type, after: split);
+      visit(node.type);
+      _separatorBetweenTypeAndVariable(node.type);
       token(node.thisKeyword);
       token(node.period);
       token(node.name);
@@ -1329,7 +1350,6 @@
     PositionalRule? rule;
     if (requiredParams.isNotEmpty) {
       rule = PositionalRule(null, argumentCount: requiredParams.length);
-      _metadataRules.last.constrainWhenFullySplit(rule);
 
       builder.startRule(rule);
       if (node.isFunctionExpressionBody) {
@@ -1342,6 +1362,8 @@
         rule.beforeArgument(zeroSplit());
       }
 
+      // Make sure record and function type parameter lists are indented.
+      builder.startBlockArgumentNesting();
       builder.startSpan();
 
       for (var param in requiredParams) {
@@ -1351,18 +1373,19 @@
         if (param != requiredParams.last) rule.beforeArgument(split());
       }
 
+      builder.endBlockArgumentNesting();
       builder.endSpan();
       builder.endRule();
     }
 
     if (optionalParams.isNotEmpty) {
       var namedRule = NamedRule(null, 0, 0);
-      _metadataRules.last.constrainWhenFullySplit(namedRule);
       if (rule != null) rule.addNamedArgsConstraints(namedRule);
 
       builder.startRule(namedRule);
 
-      // Make sure multi-line default values are indented.
+      // Make sure multi-line default values, record types, and inner function
+      // types are indented.
       builder.startBlockArgumentNesting();
 
       namedRule.beforeArgument(builder.split(space: requiredParams.isNotEmpty));
@@ -1412,7 +1435,7 @@
     builder.endRule();
     builder.unnest();
 
-    builder.nestExpression(indent: 2, now: true);
+    builder.nestExpression(indent: Indent.block, now: true);
 
     if (isSpreadBody) {
       space();
@@ -1507,6 +1530,17 @@
   }
 
   @override
+  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
+    builder.startBlockArgumentNesting();
+    visitNodes(node.metadata, between: split, after: split);
+    token(node.keyword);
+    space();
+    visit(node.pattern);
+    builder.endBlockArgumentNesting();
+    _visitForEachPartsFromIn(node);
+  }
+
+  @override
   void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
     // Nest split variables more so they aren't at the same level
     // as the rest of the loop clauses.
@@ -1516,7 +1550,7 @@
     builder.startRule();
 
     var declaration = node.variables;
-    visitMetadata(declaration.metadata);
+    visitNodes(declaration.metadata, between: split, after: split);
     modifier(declaration.keyword);
     visit(declaration.type, after: space);
 
@@ -1536,6 +1570,24 @@
     _visitForPartsFromLeftSeparator(node);
   }
 
+  @override
+  void visitForPartsWithPattern(ForPartsWithPattern node) {
+    builder.startBlockArgumentNesting();
+    builder.nestExpression();
+
+    var declaration = node.variables;
+    visitNodes(declaration.metadata, between: split, after: split);
+    token(declaration.keyword);
+    space();
+    visit(declaration.pattern);
+    _visitAssignment(declaration.equals, declaration.expression);
+
+    builder.unnest();
+    builder.endBlockArgumentNesting();
+
+    _visitForPartsFromLeftSeparator(node);
+  }
+
   void _visitForPartsFromLeftSeparator(ForParts node) {
     token(node.leftSeparator);
 
@@ -1559,7 +1611,18 @@
 
   @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
-    _visitMemberDeclaration(node, node.functionExpression);
+    _visitFunctionOrMethodDeclaration(
+      metadata: node.metadata,
+      externalKeyword: node.externalKeyword,
+      propertyKeyword: node.propertyKeyword,
+      modifierKeyword: null,
+      operatorKeyword: null,
+      name: node.name,
+      returnType: node.returnType,
+      typeParameters: node.functionExpression.typeParameters,
+      formalParameters: node.functionExpression.parameters,
+      body: node.functionExpression.body,
+    );
   }
 
   @override
@@ -1607,17 +1670,17 @@
         // Inlined visitGenericTypeAlias
         _visitGenericTypeAliasHeader(
             node.typedefKeyword,
-            node.name2,
+            node.name,
             node.typeParameters,
             null,
-            node.returnType?.beginToken ?? node.name2);
+            node.returnType?.beginToken ?? node.name);
 
         space();
 
         // Recursively convert function-arguments to Function syntax.
         _insideNewTypedefFix = true;
         _visitGenericFunctionType(
-            node.returnType, null, node.name2, null, node.parameters);
+            node.returnType, null, node.name, null, node.parameters);
         _insideNewTypedefFix = false;
       });
       return;
@@ -1627,7 +1690,7 @@
       token(node.typedefKeyword);
       space();
       visit(node.returnType, after: space);
-      token(node.name2);
+      token(node.name);
       visit(node.typeParameters);
       visit(node.parameters);
     });
@@ -1669,7 +1732,7 @@
   void visitGenericTypeAlias(GenericTypeAlias node) {
     visitNodes(node.metadata, between: newline, after: newline);
     _simpleStatement(node, () {
-      _visitGenericTypeAliasHeader(node.typedefKeyword, node.name2,
+      _visitGenericTypeAliasHeader(node.typedefKeyword, node.name,
           node.typeParameters, node.equals, null);
 
       space();
@@ -1763,12 +1826,8 @@
 
     var hasInnerControlFlow = false;
     for (var element in ifElements) {
-      // The condition.
-      token(element.ifKeyword);
-      space();
-      token(element.leftParenthesis);
-      visit(element.condition);
-      token(element.rightParenthesis);
+      _visitIfCondition(element.ifKeyword, element.leftParenthesis,
+          element.condition, element.caseClause, element.rightParenthesis);
 
       visitChild(element, element.thenElement);
       if (element.thenElement.isControlFlowElement) {
@@ -1811,13 +1870,8 @@
 
   @override
   void visitIfStatement(IfStatement node) {
-    builder.nestExpression();
-    token(node.ifKeyword);
-    space();
-    token(node.leftParenthesis);
-    visit(node.condition);
-    token(node.rightParenthesis);
-    builder.unnest();
+    _visitIfCondition(node.ifKeyword, node.leftParenthesis, node.condition,
+        node.caseClause, node.rightParenthesis);
 
     void visitClause(Statement clause) {
       if (clause is Block || clause is IfStatement) {
@@ -2021,8 +2075,9 @@
     _visitDirectiveMetadata(node);
     _simpleStatement(node, () {
       token(node.libraryKeyword);
-      space();
-      visit(node.name);
+      if (node.name2 != null) {
+        visit(node.name2, before: space);
+      }
     });
   }
 
@@ -2040,8 +2095,33 @@
     // Corner case: Splitting inside a list looks bad if there's only one
     // element, so make those more costly.
     var cost = node.elements.length <= 1 ? Cost.singleElementList : Cost.normal;
-    _visitCollectionLiteral(
-        node, node.leftBracket, node.elements, node.rightBracket, cost);
+    _visitCollectionLiteral(node.leftBracket, node.elements, node.rightBracket,
+        constKeyword: node.constKeyword,
+        typeArguments: node.typeArguments,
+        splitOuterCollection: true,
+        cost: cost);
+  }
+
+  @override
+  void visitListPattern(ListPattern node) {
+    _visitCollectionLiteral(node.leftBracket, node.elements, node.rightBracket,
+        typeArguments: node.typeArguments);
+  }
+
+  @override
+  void visitLogicalAndPattern(LogicalAndPattern node) {
+    _visitBinary<LogicalAndPattern>(
+        node,
+        (pattern) => BinaryNode(
+            pattern.leftOperand, pattern.operator, pattern.rightOperand));
+  }
+
+  @override
+  void visitLogicalOrPattern(LogicalOrPattern node) {
+    _visitBinary<LogicalOrPattern>(
+        node,
+        (pattern) => BinaryNode(
+            pattern.leftOperand, pattern.operator, pattern.rightOperand));
   }
 
   @override
@@ -2055,8 +2135,35 @@
   }
 
   @override
+  void visitMapPattern(MapPattern node) {
+    _visitCollectionLiteral(node.leftBracket, node.elements, node.rightBracket,
+        typeArguments: node.typeArguments);
+  }
+
+  @override
+  void visitMapPatternEntry(MapPatternEntry node) {
+    builder.nestExpression();
+    visit(node.key);
+    token(node.separator);
+    soloSplit();
+    visit(node.value);
+    builder.unnest();
+  }
+
+  @override
   void visitMethodDeclaration(MethodDeclaration node) {
-    _visitMemberDeclaration(node, node);
+    _visitFunctionOrMethodDeclaration(
+      metadata: node.metadata,
+      externalKeyword: node.externalKeyword,
+      propertyKeyword: node.propertyKeyword,
+      modifierKeyword: node.modifierKeyword,
+      operatorKeyword: node.operatorKeyword,
+      name: node.name,
+      returnType: node.returnType,
+      typeParameters: node.typeParameters,
+      formalParameters: node.parameters,
+      body: node.body,
+    );
   }
 
   @override
@@ -2119,9 +2226,10 @@
     visitMetadata(node.metadata);
 
     builder.nestExpression();
+    modifier(node.baseKeyword);
     token(node.mixinKeyword);
     space();
-    token(node.name2);
+    token(node.name);
     visit(node.typeParameters);
 
     // If there is only a single superclass constraint, format it like an
@@ -2153,7 +2261,7 @@
 
   @override
   void visitNamedExpression(NamedExpression node) {
-    visitNamedArgument(node);
+    visitNamedNode(node.name.label.token, node.name.colon, node.expression);
   }
 
   @override
@@ -2181,11 +2289,55 @@
   }
 
   @override
+  void visitNullAssertPattern(NullAssertPattern node) {
+    visit(node.pattern);
+    token(node.operator);
+  }
+
+  @override
+  void visitNullCheckPattern(NullCheckPattern node) {
+    visit(node.pattern);
+    token(node.operator);
+  }
+
+  @override
   void visitNullLiteral(NullLiteral node) {
     token(node.literal);
   }
 
   @override
+  void visitObjectPattern(ObjectPattern node) {
+    // Even though object patterns syntactically resemble constructor or
+    // function calls, we format them like collections (or like argument lists
+    // with trailing commas). In other words, like this:
+    //
+    //     case Foo(
+    //         first: 1,
+    //         second: 2,
+    //         third: 3
+    //       ):
+    //       body;
+    //
+    // Not like:
+    //
+    //     case Foo(
+    //           first: 1,
+    //           second: 2,
+    //           third: 3):
+    //       body;
+    //
+    // This is less consistent with the corresponding expression form, but is
+    // more consistent with all of the other delimited patterns -- list, map,
+    // and record -- which have collection-like formatting.
+    // TODO(rnystrom): If we move to consistently using collection-like
+    // formatting for all argument lists, then this will all be consistent and
+    // this comment should be removed.
+    visit(node.type);
+    _visitCollectionLiteral(
+        node.leftParenthesis, node.fields, node.rightParenthesis);
+  }
+
+  @override
   void visitOnClause(OnClause node) {
     _visitCombinator(node.onKeyword, node.superclassConstraints);
   }
@@ -2200,6 +2352,15 @@
   }
 
   @override
+  void visitParenthesizedPattern(ParenthesizedPattern node) {
+    builder.nestExpression();
+    token(node.leftParenthesis);
+    visit(node.pattern);
+    builder.unnest();
+    token(node.rightParenthesis);
+  }
+
+  @override
   void visitPartDirective(PartDirective node) {
     _visitDirectiveMetadata(node);
     _simpleStatement(node, () {
@@ -2226,6 +2387,49 @@
   }
 
   @override
+  void visitPatternAssignment(PatternAssignment node) {
+    visit(node.pattern);
+    _visitAssignment(node.equals, node.expression);
+  }
+
+  @override
+  void visitPatternField(PatternField node) {
+    var fieldName = node.name;
+    if (fieldName != null) {
+      var name = fieldName.name;
+      if (name != null) {
+        visitNamedNode(fieldName.name!, fieldName.colon, node.pattern);
+      } else {
+        // Named field with inferred name, like:
+        //
+        //     var (:x) = (x: 1);
+        token(fieldName.colon);
+        visit(node.pattern);
+      }
+    } else {
+      visit(node.pattern);
+    }
+  }
+
+  @override
+  void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
+    visitMetadata(node.metadata);
+    builder.nestExpression();
+    token(node.keyword);
+    space();
+    visit(node.pattern);
+    _visitAssignment(node.equals, node.expression);
+    builder.unnest();
+  }
+
+  @override
+  void visitPatternVariableDeclarationStatement(
+      PatternVariableDeclarationStatement node) {
+    visit(node.declaration);
+    token(node.semicolon);
+  }
+
+  @override
   void visitPostfixExpression(PostfixExpression node) {
     visit(node.operand);
     token(node.operator);
@@ -2276,11 +2480,136 @@
   }
 
   @override
+  void visitRecordLiteral(RecordLiteral node) {
+    modifier(node.constKeyword);
+    _visitCollectionLiteral(
+        node.leftParenthesis, node.fields, node.rightParenthesis,
+        isRecord: true);
+  }
+
+  @override
+  void visitRecordPattern(RecordPattern node) {
+    _visitCollectionLiteral(
+        node.leftParenthesis, node.fields, node.rightParenthesis,
+        isRecord: true);
+  }
+
+  @override
+  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
+    var namedFields = node.namedFields;
+
+    // Handle empty record types specially.
+    if (node.positionalFields.isEmpty && namedFields == null) {
+      token(node.leftParenthesis);
+
+      // If there is a comment inside the parens, do allow splitting before it.
+      if (node.rightParenthesis.precedingComments != null) soloZeroSplit();
+
+      token(node.rightParenthesis);
+      return;
+    }
+
+    _metadataRules.add(Rule());
+    token(node.leftParenthesis);
+    builder.startRule();
+
+    // If all parameters are named, put the "{" right after "(".
+    if (node.positionalFields.isEmpty) {
+      token(namedFields!.leftBracket);
+    }
+
+    // Process the parameters as a separate set of chunks.
+    builder = builder.startBlock();
+
+    // Write the positional fields.
+    for (var field in node.positionalFields) {
+      builder.split(nest: false, space: field != node.positionalFields.first);
+      visit(field);
+      _writeCommaAfter(field);
+    }
+
+    // Then the named fields.
+    var firstClosingDelimiter = node.rightParenthesis;
+    if (namedFields != null) {
+      if (node.positionalFields.isNotEmpty) {
+        space();
+        token(namedFields.leftBracket);
+      }
+
+      for (var field in namedFields.fields) {
+        builder.split(nest: false, space: field != namedFields.fields.first);
+        visit(field);
+        _writeCommaAfter(field);
+      }
+
+      firstClosingDelimiter = namedFields.rightBracket;
+    }
+
+    // Put comments before the closing ")" or "}" inside the block.
+    if (firstClosingDelimiter.precedingComments != null) {
+      newline();
+      writePrecedingCommentsAndNewlines(firstClosingDelimiter);
+    }
+
+    // If there is a trailing comma, then force the record type to split. But
+    // don't force if there is only a single positional element because then
+    // the trailing comma is actually mandatory.
+    bool force;
+    if (namedFields == null) {
+      force = node.positionalFields.length > 1 &&
+          node.positionalFields.last.hasCommaAfter;
+    } else {
+      force = namedFields.fields.last.hasCommaAfter;
+    }
+
+    builder = builder.endBlock(forceSplit: force);
+    builder.endRule();
+    _metadataRules.removeLast();
+
+    // Now write the delimiter(s) themselves.
+    _writeText(firstClosingDelimiter.lexeme, firstClosingDelimiter);
+    if (namedFields != null) token(node.rightParenthesis);
+
+    token(node.question);
+  }
+
+  @override
+  void visitRecordTypeAnnotationNamedField(
+      RecordTypeAnnotationNamedField node) {
+    visitParameterMetadata(node.metadata, () {
+      visit(node.type);
+      token(node.name, before: space);
+    });
+  }
+
+  @override
+  void visitRecordTypeAnnotationPositionalField(
+      RecordTypeAnnotationPositionalField node) {
+    visitParameterMetadata(node.metadata, () {
+      visit(node.type);
+      token(node.name, before: space);
+    });
+  }
+
+  @override
+  void visitRelationalPattern(RelationalPattern node) {
+    token(node.operator);
+    space();
+    visit(node.operand);
+  }
+
+  @override
   void visitRethrowExpression(RethrowExpression node) {
     token(node.rethrowKeyword);
   }
 
   @override
+  void visitRestPatternElement(RestPatternElement node) {
+    token(node.operator);
+    visit(node.pattern);
+  }
+
+  @override
   void visitReturnStatement(ReturnStatement node) {
     _simpleStatement(node, () {
       token(node.returnKeyword);
@@ -2300,8 +2629,10 @@
 
   @override
   void visitSetOrMapLiteral(SetOrMapLiteral node) {
-    _visitCollectionLiteral(
-        node, node.leftBracket, node.elements, node.rightBracket);
+    _visitCollectionLiteral(node.leftBracket, node.elements, node.rightBracket,
+        constKeyword: node.constKeyword,
+        typeArguments: node.typeArguments,
+        splitOuterCollection: true);
   }
 
   @override
@@ -2314,8 +2645,7 @@
     visitParameterMetadata(node.metadata, () {
       _beginFormalParameter(node);
 
-      var hasType = node.type != null;
-      if (_insideNewTypedefFix && !hasType) {
+      if (_insideNewTypedefFix && node.type == null) {
         // Parameters can use "var" instead of "dynamic". Since we are inserting
         // "dynamic" in that case, remove the "var".
         if (node.keyword != null) {
@@ -2338,10 +2668,9 @@
         });
       } else {
         modifier(node.keyword);
+
         visit(node.type);
-
-        if (hasType && node.name != null) split();
-
+        if (node.name != null) _separatorBetweenTypeAndVariable(node.type);
         token(node.name);
       }
 
@@ -2405,50 +2734,194 @@
   }
 
   @override
-  void visitSwitchCase(SwitchCase node) {
-    _visitLabels(node.labels);
-    token(node.keyword);
-    space();
-    visit(node.expression);
-    token(node.colon);
+  void visitSwitchExpression(SwitchExpression node) {
+    if (node.cases.isEmptyBody(node.rightBracket)) {
+      // Don't allow splitting an empty switch expression.
+      _visitSwitchValue(node.switchKeyword, node.leftParenthesis,
+          node.expression, node.rightParenthesis);
+      token(node.leftBracket);
+      token(node.rightBracket);
+      return;
+    }
 
-    builder.indent();
-    // TODO(rnystrom): Allow inline cases?
-    newline();
+    // Start the rule for splitting between the cases before the value. That
+    // way, if the value expression splits, the cases do too. Avoids:
+    //
+    //     switch ([
+    //        element,
+    //     ]) { inline => caseBody };
+    builder.startRule();
 
-    visitNodes(node.statements, between: oneOrTwoNewlines);
-    builder.unindent();
+    _visitSwitchValue(node.switchKeyword, node.leftParenthesis, node.expression,
+        node.rightParenthesis);
+
+    token(node.leftBracket);
+    builder = builder.startBlock(space: node.cases.isNotEmpty);
+
+    visitCommaSeparatedNodes(node.cases, between: split);
+
+    var hasTrailingComma =
+        node.cases.isNotEmpty && node.cases.last.commaAfter != null;
+    _endBody(node.rightBracket, forceSplit: hasTrailingComma);
   }
 
   @override
-  void visitSwitchDefault(SwitchDefault node) {
-    _visitLabels(node.labels);
-    token(node.keyword);
-    token(node.colon);
+  void visitSwitchExpressionCase(SwitchExpressionCase node) {
+    // If the pattern is a series of `||` patterns, then flatten them out and
+    // format them like empty cases with fallthrough in a switch statement
+    // instead of like a single indented binary pattern. Prefer:
+    //
+    //   e = switch (obj) {
+    //     constant1 ||
+    //     constant2 ||
+    //     constant3 =>
+    //       body
+    //   };
+    //
+    // Instead of:
+    //
+    //   e = switch (obj) {
+    //     constant1 ||
+    //        constant2 ||
+    //        constant3 =>
+    //       body
+    //   };
+    var orBranches = <DartPattern>[];
+    var orTokens = <Token>[];
 
-    builder.indent();
-    // TODO(rnystrom): Allow inline cases?
-    newline();
+    void flattenOr(DartPattern e) {
+      if (e is! LogicalOrPattern) {
+        orBranches.add(e);
+      } else {
+        flattenOr(e.leftOperand);
+        orTokens.add(e.operator);
+        flattenOr(e.rightOperand);
+      }
+    }
 
-    visitNodes(node.statements, between: oneOrTwoNewlines);
-    builder.unindent();
+    flattenOr(node.guardedPattern.pattern);
+
+    // Wrap the rule for splitting after "=>" around the pattern so that a
+    // split in the pattern forces the expression to move to the next line too.
+    builder.startLazyRule();
+
+    // Write the "||" operands up to the last one.
+    for (var i = 0; i < orBranches.length - 1; i++) {
+      // Note that orBranches will always have one more element than orTokens.
+      visit(orBranches[i]);
+      space();
+      token(orTokens[i]);
+      split();
+    }
+
+    // Wrap the expression's nesting around the final pattern so that a split in
+    // the pattern is indented farther then the body expression. Used +2 indent
+    // because switch expressions are block-like, similar to how we split the
+    // bodies of if and for elements in collections.
+    builder.nestExpression(indent: Indent.block);
+
+    var whenClause = node.guardedPattern.whenClause;
+    if (whenClause != null) {
+      // Wrap the when clause rule around the pattern so that if the pattern
+      // splits then we split before "when" too.
+      builder.startRule();
+      builder.nestExpression(indent: Indent.block);
+    }
+
+    // Write the last pattern in the "||" chain. If the case pattern isn't an
+    // "||" pattern at all, this writes the one pattern.
+    visit(orBranches.last);
+
+    if (whenClause != null) {
+      split();
+      builder.startBlockArgumentNesting();
+      _visitWhenClause(whenClause);
+      builder.endBlockArgumentNesting();
+      builder.unnest();
+      builder.endRule();
+    }
+
+    space();
+    token(node.arrow);
+    split();
+    builder.endRule();
+
+    builder.startBlockArgumentNesting();
+    visit(node.expression);
+    builder.endBlockArgumentNesting();
+
+    builder.unnest();
   }
 
   @override
   void visitSwitchStatement(SwitchStatement node) {
+    _visitSwitchValue(node.switchKeyword, node.leftParenthesis, node.expression,
+        node.rightParenthesis);
+    _beginBody(node.leftBracket);
+    for (var member in node.members) {
+      _visitLabels(member.labels);
+      token(member.keyword);
+
+      if (member is SwitchCase) {
+        space();
+        visit(member.expression);
+      } else if (member is SwitchPatternCase) {
+        space();
+        var whenClause = member.guardedPattern.whenClause;
+        if (whenClause == null) {
+          builder.indent();
+          visit(member.guardedPattern.pattern);
+          builder.unindent();
+        } else {
+          // Wrap the when clause rule around the pattern so that if the pattern
+          // splits then we split before "when" too.
+          builder.startRule();
+          builder.nestExpression();
+          builder.startBlockArgumentNesting();
+          visit(member.guardedPattern.pattern);
+          split();
+          _visitWhenClause(whenClause);
+          builder.endBlockArgumentNesting();
+          builder.unnest();
+          builder.endRule();
+        }
+      } else {
+        assert(member is SwitchDefault);
+        // Nothing to do.
+      }
+
+      token(member.colon);
+
+      if (member.statements.isNotEmpty) {
+        builder.indent();
+        newline();
+        visitNodes(member.statements, between: oneOrTwoNewlines);
+        builder.unindent();
+        oneOrTwoNewlines();
+      } else {
+        // Don't preserve blank lines between empty cases.
+        builder.writeNewline();
+      }
+    }
+
+    if (node.members.isNotEmpty) {
+      newline();
+    }
+    _endBody(node.rightBracket, forceSplit: node.members.isNotEmpty);
+  }
+
+  /// Visits the `switch (expr)` part of a switch statement or expression.
+  void _visitSwitchValue(Token switchKeyword, Token leftParenthesis,
+      Expression value, Token rightParenthesis) {
     builder.nestExpression();
-    token(node.switchKeyword);
+    token(switchKeyword);
     space();
-    token(node.leftParenthesis);
+    token(leftParenthesis);
     soloZeroSplit();
-    visit(node.expression);
-    token(node.rightParenthesis);
+    visit(value);
+    token(rightParenthesis);
     space();
     builder.unnest();
-
-    _beginBody(node.leftBracket);
-    visitNodes(node.members, between: oneOrTwoNewlines, after: newline);
-    _endBody(node.rightBracket, forceSplit: true);
   }
 
   @override
@@ -2504,7 +2977,7 @@
   @override
   void visitTypeParameter(TypeParameter node) {
     visitParameterMetadata(node.metadata, () {
-      token(node.name2);
+      token(node.name);
       token(node.extendsKeyword, before: space, after: space);
       visit(node.bound);
     });
@@ -2521,7 +2994,7 @@
 
   @override
   void visitVariableDeclaration(VariableDeclaration node) {
-    token(node.name2);
+    token(node.name);
     if (node.initializer == null) return;
 
     // If there are multiple variables being declared, we want to nest the
@@ -2554,7 +3027,8 @@
 
     modifier(node.lateKeyword);
     modifier(node.keyword);
-    visit(node.type, after: soloSplit);
+    visit(node.type);
+    _separatorBetweenTypeAndVariable(node.type, isSolo: true);
 
     builder.endSpan();
 
@@ -2600,6 +3074,11 @@
   }
 
   @override
+  void visitWildcardPattern(WildcardPattern node) {
+    _visitVariablePattern(node.keyword, node.type, node.name);
+  }
+
+  @override
   void visitWithClause(WithClause node) {
     _visitCombinator(node.withKeyword, node.mixinTypes);
   }
@@ -2688,20 +3167,31 @@
   /// split between the name and argument forces the argument list to split
   /// too.
   void visitNamedArgument(NamedExpression node, [NamedRule? rule]) {
+    visitNamedNode(
+        node.name.label.token, node.name.colon, node.expression, rule);
+  }
+
+  /// Visits syntax of the form `identifier: <node>`: a named argument or a
+  /// named record field.
+  void visitNamedNode(Token name, Token colon, AstNode node,
+      [NamedRule? rule]) {
     builder.nestExpression();
     builder.startSpan();
-    visit(node.name);
+    token(name);
+    token(colon);
 
     // Don't allow a split between a name and a collection. Instead, we want
     // the collection itself to split, or to split before the argument.
-    if (node.expression is ListLiteral || node.expression is SetOrMapLiteral) {
+    if (node is ListLiteral ||
+        node is SetOrMapLiteral ||
+        node is RecordLiteral) {
       space();
     } else {
       var split = soloSplit();
       if (rule != null) split.constrainWhenSplit(rule);
     }
 
-    visit(node.expression);
+    visit(node);
     builder.endSpan();
     builder.unnest();
   }
@@ -2730,6 +3220,67 @@
     if (nest) builder.unnest();
   }
 
+  /// Visits an infix operator-like AST node: a binary operator expression, or
+  /// binary pattern.
+  ///
+  /// In a tree of binary AST nodes, all operators at the same precedence are
+  /// treated as a single chain of operators that either all split or none do.
+  /// Operands within those (which may themselves be chains of higher
+  /// precedence binary operators) are then formatted independently.
+  ///
+  /// [T] is the type of node being visited and [destructureNode] is a callback
+  /// that takes one of those and yields the operands and operator. We need
+  /// this since there's no interface shared by the various binary operator
+  /// AST nodes.
+  ///
+  /// If [precedence] is given, then only flattens binary nodes with that same
+  /// precedence. If [nest] is `false`, then elides the nesting around the
+  /// expression.
+  void _visitBinary<T extends AstNode>(
+      T node, BinaryNode Function(T node) destructureNode,
+      {int? precedence, bool nest = true}) {
+    builder.startSpan();
+
+    if (nest) builder.nestExpression();
+
+    // Start lazily so we don't force the operator to split if a line comment
+    // appears before the first operand.
+    builder.startLazyRule();
+
+    // Blocks as operands to infix operators should always nest like regular
+    // operands. (Granted, this case is exceedingly rare in real code.)
+    builder.startBlockArgumentNesting();
+
+    void traverse(AstNode e) {
+      if (e is! T) {
+        visit(e);
+      } else {
+        var binary = destructureNode(e);
+        if (precedence != null &&
+            binary.operator.type.precedence != precedence) {
+          // Binary node, but a different precedence, so don't flatten.
+          visit(e);
+        } else {
+          traverse(binary.left);
+
+          space();
+          token(binary.operator);
+
+          split();
+          traverse(binary.right);
+        }
+      }
+    }
+
+    traverse(node);
+
+    builder.endBlockArgumentNesting();
+
+    if (nest) builder.unnest();
+    builder.endSpan();
+    builder.endRule();
+  }
+
   /// Visits the "with" and "implements" clauses in a type declaration.
   void _visitClauses(
       WithClause? withClause, ImplementsClause? implementsClause) {
@@ -2757,18 +3308,30 @@
     token(leftBracket);
     rule.beforeArgument(zeroSplit());
 
+    // Set the block nesting in case an argument is a function type with a
+    // trailing comma or a record type.
+    builder.startBlockArgumentNesting();
+
     for (var node in nodes) {
       visit(node);
 
-      // Write the trailing comma.
+      // Write the comma separator.
       if (node != nodes.last) {
-        token(node.endToken.next);
+        var comma = node.endToken.next;
+
+        // TODO(rnystrom): There is a bug in analyzer where the end token of a
+        // nullable record type is the ")" and not the "?". This works around
+        // that. Remove that's fixed.
+        if (comma?.lexeme == '?') comma = comma?.next;
+
+        token(comma);
         rule.beforeArgument(split());
       }
     }
 
     token(rightBracket);
 
+    builder.endBlockArgumentNesting();
     builder.unnest();
     builder.endSpan();
     builder.endRule();
@@ -2796,42 +3359,49 @@
     }
   }
 
+  /// Visits a variable or wildcard pattern.
+  void _visitVariablePattern(Token? keyword, TypeAnnotation? type, Token name) {
+    modifier(keyword);
+    visit(type, after: soloSplit);
+    token(name);
+  }
+
   /// Visits a top-level function or method declaration.
-  ///
-  /// The two AST node types are very similar but, alas, share no common
-  /// interface type in analyzer, hence the dynamic typing.
-  void _visitMemberDeclaration(/* FunctionDeclaration|MethodDeclaration */ node,
-      /* FunctionExpression|MethodDeclaration */ function) {
-    visitMetadata(node.metadata as NodeList<Annotation>);
+  void _visitFunctionOrMethodDeclaration({
+    required NodeList<Annotation> metadata,
+    required Token? externalKeyword,
+    required Token? propertyKeyword,
+    required Token? modifierKeyword,
+    required Token? operatorKeyword,
+    required Token name,
+    required TypeAnnotation? returnType,
+    required TypeParameterList? typeParameters,
+    required FormalParameterList? formalParameters,
+    required FunctionBody body,
+  }) {
+    visitMetadata(metadata);
 
     // Nest the signature in case we have to split between the return type and
     // name.
     builder.nestExpression();
     builder.startSpan();
-    modifier(node.externalKeyword);
-    if (node is MethodDeclaration) modifier(node.modifierKeyword);
-    visit(node.returnType, after: soloSplit);
-    modifier(node.propertyKeyword);
-    if (node is MethodDeclaration) modifier(node.operatorKeyword);
-    token(node.name2);
+    modifier(externalKeyword);
+    modifier(modifierKeyword);
+    visit(returnType, after: soloSplit);
+    modifier(propertyKeyword);
+    modifier(operatorKeyword);
+    token(name);
     builder.endSpan();
 
-    TypeParameterList? typeParameters;
-    if (node is FunctionDeclaration) {
-      typeParameters = node.functionExpression.typeParameters;
-    } else {
-      typeParameters = (node as MethodDeclaration).typeParameters;
-    }
-
-    _visitFunctionBody(typeParameters, function.parameters, function.body, () {
+    _visitFunctionBody(typeParameters, formalParameters, body, () {
       // If the body is a block, we need to exit nesting before we hit the body
       // indentation, but we do want to wrap it around the parameters.
-      if (function.body is! ExpressionFunctionBody) builder.unnest();
+      if (body is! ExpressionFunctionBody) builder.unnest();
     });
 
     // If it's an expression, we want to wrap the nesting around that so that
     // the body gets nested farther.
-    if (function.body is ExpressionFunctionBody) builder.unnest();
+    if (body is ExpressionFunctionBody) builder.unnest();
   }
 
   /// Visit the given function [parameters] followed by its [body], printing a
@@ -2956,26 +3526,67 @@
     }
   }
 
-  /// Visits the collection literal [node] whose body starts with [leftBracket],
+  /// Visits the construct whose body starts with [leftBracket],
   /// ends with [rightBracket] and contains [elements].
   ///
-  /// This is also used for argument lists with a trailing comma which are
-  /// considered "collection-like". In that case, [node] is `null`.
-  void _visitCollectionLiteral(TypedLiteral? node, Token leftBracket,
-      List<AstNode> elements, Token rightBracket,
-      [int? cost]) {
-    if (node != null) {
-      // See if `const` should be removed.
-      if (node.constKeyword != null &&
-          _constNesting > 0 &&
-          _formatter.fixes.contains(StyleFix.optionalConst)) {
-        // Don't lose comments before the discarded keyword, if any.
-        writePrecedingCommentsAndNewlines(node.constKeyword!);
-      } else {
-        modifier(node.constKeyword);
+  /// This is used for collection literals, collection patterns, and argument
+  /// lists with a trailing comma which are considered "collection-like".
+  ///
+  /// If [splitOuterCollection] is `true` then this collection forces any
+  /// surrounding collections to split even if this one doesn't. We do this for
+  /// collection literals, but not other collection-like constructs.
+  void _visitCollectionLiteral(
+      Token leftBracket, List<AstNode> elements, Token rightBracket,
+      {Token? constKeyword,
+      TypeArgumentList? typeArguments,
+      int? cost,
+      bool splitOuterCollection = false,
+      bool isRecord = false}) {
+    // See if `const` should be removed.
+    if (constKeyword != null &&
+        _constNesting > 0 &&
+        _formatter.fixes.contains(StyleFix.optionalConst)) {
+      // Don't lose comments before the discarded keyword, if any.
+      writePrecedingCommentsAndNewlines(constKeyword);
+    } else {
+      modifier(constKeyword);
+    }
+
+    // Don't use the normal type argument list formatting code because we don't
+    // want to allow splitting before the "<" since there is no preceding
+    // identifier and it looks weird to have a "<" hanging by itself. Prevents:
+    //
+    //   var list = <
+    //       LongTypeName<
+    //           TypeArgument,
+    //           TypeArgument>>[];
+    if (typeArguments != null) {
+      builder.startSpan();
+      builder.nestExpression();
+      token(typeArguments.leftBracket);
+      builder.startRule(Rule(Cost.typeArgument));
+
+      for (var typeArgument in typeArguments.arguments) {
+        visit(typeArgument);
+
+        // Write the comma separator.
+        if (typeArgument != typeArguments.arguments.last) {
+          var comma = typeArgument.endToken.next;
+
+          // TODO(rnystrom): There is a bug in analyzer where the end token of a
+          // nullable record type is the ")" and not the "?". This works around
+          // that. Remove that's fixed.
+          if (comma?.lexeme == '?') comma = comma?.next;
+
+          token(comma);
+          split();
+        }
       }
 
-      visit(node.typeArguments);
+      token(typeArguments.rightBracket);
+      builder.endRule();
+      builder.unnest();
+      builder.endSpan();
     }
 
     // Handle empty collections, with or without comments.
@@ -2984,16 +3595,17 @@
       return;
     }
 
-    // Force all of the surrounding collections to split.
-    for (var i = 0; i < _collectionSplits.length; i++) {
-      _collectionSplits[i] = true;
+    // Unlike other collections, records don't force outer ones to split.
+    if (splitOuterCollection) {
+      // Force all of the surrounding collections to split.
+      _collectionSplits.fillRange(0, _collectionSplits.length, true);
+
+      // Add this collection to the stack.
+      _collectionSplits.add(false);
     }
 
-    // Add this collection to the stack.
-    _collectionSplits.add(false);
-
     _beginBody(leftBracket);
-    if (node != null) _startPossibleConstContext(node.constKeyword);
+    _startPossibleConstContext(constKeyword);
 
     // If a collection contains a line comment, we assume it's a big complex
     // blob of data with some documented structure. In that case, the user
@@ -3034,12 +3646,18 @@
     }
 
     // If there is a collection inside this one, it forces this one to split.
-    var force = _collectionSplits.removeLast();
+    var force = false;
+    if (splitOuterCollection) {
+      force = _collectionSplits.removeLast();
+    }
 
     // If the collection has a trailing comma, the user must want it to split.
-    if (elements.hasCommaAfter) force = true;
+    // (Unless it's a single-element record literal, in which case the trailing
+    // comma is required for disambiguation.)
+    var isSingleElementRecord = isRecord && elements.length == 1;
+    if (elements.hasCommaAfter && !isSingleElementRecord) force = true;
 
-    if (node != null) _endPossibleConstContext(node.constKeyword);
+    _endPossibleConstContext(constKeyword);
     _endBody(rightBracket, forceSplit: force);
   }
 
@@ -3082,7 +3700,7 @@
     builder = builder.startBlock();
 
     for (var parameter in parameters.parameters) {
-      newline();
+      builder.writeNewline(preventDivide: true);
       visit(parameter);
       _writeCommaAfter(parameter);
 
@@ -3099,7 +3717,7 @@
     var firstDelimiter =
         parameters.rightDelimiter ?? parameters.rightParenthesis;
     if (firstDelimiter.precedingComments != null) {
-      newline();
+      builder.writeNewline(preventDivide: true);
       writePrecedingCommentsAndNewlines(firstDelimiter);
     }
 
@@ -3184,6 +3802,98 @@
     builder.endRule();
   }
 
+  /// Visits the `if (<expr> [case <pattern> [when <expr>]])` header of an if
+  /// statement or element.
+  void _visitIfCondition(Token ifKeyword, Token leftParenthesis,
+      AstNode condition, CaseClause? caseClause, Token rightParenthesis) {
+    builder.nestExpression();
+    token(ifKeyword);
+    space();
+    token(leftParenthesis);
+
+    if (caseClause == null) {
+      // Simple if with no "case".
+      visit(condition);
+    } else {
+      // If-case.
+
+      // Wrap the rule for splitting before "case" around the value expression
+      // so that if the value splits, we split before "case" too.
+      var caseRule = Rule();
+      builder.startRule(caseRule);
+
+      visit(condition);
+
+      // "case" and pattern.
+      split();
+      token(caseClause.caseKeyword);
+      space();
+      builder.startBlockArgumentNesting();
+      builder.nestExpression(now: true);
+      visit(caseClause.guardedPattern.pattern);
+      builder.unnest();
+      builder.endBlockArgumentNesting();
+
+      builder.endRule(); // Case rule.
+
+      var whenClause = caseClause.guardedPattern.whenClause;
+      if (whenClause != null) {
+        // Wrap the rule for "when" around the guard so that a split in the
+        // guard splits at "when" too.
+        builder.startRule();
+        split();
+        builder.startBlockArgumentNesting();
+        builder.nestExpression();
+        _visitWhenClause(whenClause);
+        builder.unnest();
+        builder.endBlockArgumentNesting();
+        builder.endRule(); // Guard rule.
+      }
+    }
+
+    token(rightParenthesis);
+    builder.unnest();
+  }
+
+  void _visitWhenClause(WhenClause whenClause) {
+    token(whenClause.whenKeyword);
+    space();
+    visit(whenClause.expression);
+  }
+
+  /// Writes the separator between a type annotation and a variable or
+  /// parameter. If the preceding type annotation ends in a delimited list of
+  /// elements that have block formatting, then we don't split between the
+  /// type annotation and parameter name, as in:
+  ///
+  ///     Function(
+  ///       int,
+  ///     ) variable;
+  ///
+  /// Otherwise, we can.
+  void _separatorBetweenTypeAndVariable(TypeAnnotation? type,
+      {bool isSolo = false}) {
+    if (type == null) return;
+
+    var isBlockType = false;
+    if (type is GenericFunctionType) {
+      // Function types get block-like formatting if they have a trailing comma.
+      isBlockType = type.parameters.parameters.isNotEmpty &&
+          type.parameters.parameters.last.hasCommaAfter;
+    } else if (type is RecordTypeAnnotation) {
+      // Record types always have block-like formatting.
+      isBlockType = true;
+    }
+
+    if (isBlockType) {
+      space();
+    } else if (isSolo) {
+      soloSplit();
+    } else {
+      split();
+    }
+  }
+
   /// Whether [node] should be forced to split even if completely empty.
   ///
   /// Most empty blocks format as `{}` but in a couple of cases where there is
@@ -3471,11 +4181,7 @@
   /// non-whitespace token based on whether any blank lines exist in the source
   /// between the last token and the next one.
   void oneOrTwoNewlines() {
-    if (_linesBeforeNextToken > 1) {
-      twoNewlines();
-    } else {
-      newline();
-    }
+    builder.writeNewline(isDouble: _linesBeforeNextToken > 1);
   }
 
   /// The number of newlines between the last written token and the next one to
@@ -3752,3 +4458,16 @@
   int _startColumn(Token token) =>
       _lineInfo.getLocation(token.offset).columnNumber;
 }
+
+/// Synthetic node for any kind of binary operator.
+///
+/// Used to share formatting logic between binary operators, logic operators,
+/// logic patterns, etc.
+// TODO: Remove this and just use a record when those are available.
+class BinaryNode {
+  final AstNode left;
+  final Token operator;
+  final AstNode right;
+
+  BinaryNode(this.left, this.operator, this.right);
+}
diff --git a/dart_style/pubspec.yaml b/dart_style/pubspec.yaml
index bfd6f2c..9ed9af5 100644
--- a/dart_style/pubspec.yaml
+++ b/dart_style/pubspec.yaml
@@ -1,15 +1,15 @@
 name: dart_style
 # Note: See tool/grind.dart for how to bump the version.
-version: 2.2.4
+version: 2.3.1
 description: >-
   Opinionated, automatic Dart source code formatter.
   Provides an API and a CLI tool.
 repository: https://github.com/dart-lang/dart_style
 environment:
-  sdk: ">=2.17.0 <3.0.0"
+  sdk: ">=2.19.0 <3.0.0"
 
 dependencies:
-  analyzer: '>=4.4.0 <6.0.0'
+  analyzer: ^5.7.0
   args: ">=1.0.0 <3.0.0"
   path: ^1.0.0
   pub_semver: ">=1.4.4 <3.0.0"
diff --git a/dds/BUILD.gn b/dds/BUILD.gn
index da30b8a..b0b27d4 100644
--- a/dds/BUILD.gn
+++ b/dds/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for dds-2.7.5
+# This file is generated by package_importer.py for dds-2.7.7
 
 import("//build/dart/dart_library.gni")
 
diff --git a/dds/CHANGELOG.md b/dds/CHANGELOG.md
index 108e830..06b3aab 100644
--- a/dds/CHANGELOG.md
+++ b/dds/CHANGELOG.md
@@ -1,3 +1,11 @@
+# 2.7.7
+- [DAP] Debug adapters now only call `setLibraryDebuggable` when the debuggable flag changes from the default/current values, reducing the amount of VM Service traffic for new isolates/reloads.
+- [DAP] `breakpoint` events are no longer sometimes sent prior to the response to the `setBreakpointsRequest` that created them.
+
+# 2.7.6
+- [DAP] `scopesRequest` now returns a `Globals` scope containing global variables for the current frame.
+- [DAP] Responses to `setBreakpointsRequest` will now have `verified: false` and will send `breakpoint` events to update `verified` and/or `line`/`column` as the VM resolves them.
+
 # 2.7.5
 - Updated `vm_service` version to >=9.0.0 <12.0.0.
 
diff --git a/dds/analysis_options.yaml b/dds/analysis_options.yaml
index 59a93d3..deffe10 100644
--- a/dds/analysis_options.yaml
+++ b/dds/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:lints/core.yaml
+include: package:lints/recommended.yaml
 
 linter:
   rules:
@@ -6,5 +6,3 @@
     - directives_ordering
     - prefer_generic_function_type_aliases
     - prefer_relative_imports
-    - unnecessary_new
-    - unnecessary_const
diff --git a/dds/lib/devtools_server.dart b/dds/lib/devtools_server.dart
index 9d30263..86dc080 100644
--- a/dds/lib/devtools_server.dart
+++ b/dds/lib/devtools_server.dart
@@ -30,8 +30,8 @@
   static const argVmUri = 'vm-uri';
   static const argEnableNotifications = 'enable-notifications';
   static const argAllowEmbedding = 'allow-embedding';
-  static const argAppSizeBase = 'appSizeBase';
-  static const argAppSizeTest = 'appSizeTest';
+  static const argAppSizeBase = 'app-size-base';
+  static const argAppSizeTest = 'app-size-test';
   static const argHeadlessMode = 'headless';
   static const argDebugMode = 'debug';
   static const argLaunchBrowser = 'launch-browser';
@@ -113,25 +113,23 @@
             'indicated file.',
       );
 
-    if (verbose) {
-      argParser.addSeparator('App size options:');
-    }
+    argParser.addSeparator('App size options:');
 
-    // TODO(devoncarew): --appSizeBase and --appSizeTest should be renamed to
-    // something like --app-size-base and --app-size-test; #3146.
     argParser
       ..addOption(
         argAppSizeBase,
-        valueHelp: 'appSizeBase',
+        aliases: ['appSizeBase'],
+        valueHelp: 'file',
         help: 'Path to the base app size file used for app size debugging.',
-        hide: !verbose,
       )
       ..addOption(
         argAppSizeTest,
-        valueHelp: 'appSizeTest',
+        aliases: ['appSizeTest'],
+        valueHelp: 'file',
         help:
             'Path to the test app size file used for app size debugging.\nThis '
-            'file should only be specified if --$argAppSizeBase is also specified.',
+            'file should only be specified if --$argAppSizeBase is also '
+            'specified.',
         hide: !verbose,
       );
 
@@ -269,28 +267,30 @@
       throw ex;
     }
 
-    final _server = server!;
+    // Type promote server.
+    server!;
+
     if (allowEmbedding) {
-      _server.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
+      server.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
       // The origin-agent-cluster header is required to support the embedding of
       // Dart DevTools in Chrome DevTools.
-      _server.defaultResponseHeaders.add('origin-agent-cluster', '?1');
+      server.defaultResponseHeaders.add('origin-agent-cluster', '?1');
     }
 
     // Ensure browsers don't cache older versions of the app.
-    _server.defaultResponseHeaders.add(
+    server.defaultResponseHeaders.add(
       HttpHeaders.cacheControlHeader,
-      'max-age=0',
+      'no-store',
     );
 
     // Serve requests in an error zone to prevent failures
     // when running from another error zone.
     runZonedGuarded(
-      () => shelf.serveRequests(_server, handler!),
+      () => shelf.serveRequests(server!, handler!),
       (e, _) => print('Error serving requests: $e'),
     );
 
-    final devToolsUrl = 'http://${_server.address.host}:${_server.port}';
+    final devToolsUrl = 'http://${server.address.host}:${server.port}';
 
     if (launchBrowser) {
       if (serviceProtocolUri != null) {
@@ -346,8 +346,8 @@
           // used `method` for the original releases.
           'method': 'server.started',
           'params': {
-            'host': _server.address.host,
-            'port': _server.port,
+            'host': server.address.host,
+            'port': server.port,
             'pid': pid,
             'protocolVersion': protocolVersion,
           }
@@ -430,8 +430,6 @@
 
     final bool verboseMode = args[argVerbose];
     final String? hostname = args[argHost];
-    final String? appSizeBase = args[argAppSizeBase];
-    final String? appSizeTest = args[argAppSizeTest];
 
     if (help) {
       print(
@@ -472,6 +470,16 @@
       profileFilename = path.absolute(profileFilename);
     }
 
+    // App size info.
+    String? appSizeBase = args[argAppSizeBase];
+    if (appSizeBase != null && !path.isAbsolute(appSizeBase)) {
+      appSizeBase = path.absolute(appSizeBase);
+    }
+    String? appSizeTest = args[argAppSizeTest];
+    if (appSizeTest != null && !path.isAbsolute(appSizeTest)) {
+      appSizeTest = path.absolute(appSizeTest);
+    }
+
     return serveDevTools(
       machineMode: machineMode,
       debugMode: debugMode,
@@ -643,7 +651,7 @@
 
     return devToolsUri
         .replace(
-            path: '${devToolsUri.path.isEmpty ? '/' : devToolsUri.path}',
+            path: devToolsUri.path.isEmpty ? '/' : devToolsUri.path,
             fragment: '?${queryStringNameValues.join('&')}')
         .toString();
   }
diff --git a/dds/lib/src/client_manager.dart b/dds/lib/src/client_manager.dart
index 807e5fd..2d42b7f 100644
--- a/dds/lib/src/client_manager.dart
+++ b/dds/lib/src/client_manager.dart
@@ -14,7 +14,7 @@
 /// [DartDevelopmentServiceClient]s, all of the same client name, with a
 /// permissions mask used to determine which pause event types require approval
 /// from one of the listed clients before resuming an isolate.
-class _ClientResumePermissions {
+class ClientResumePermissions {
   final List<DartDevelopmentServiceClient> clients = [];
   int permissionsMask = 0;
 }
@@ -110,7 +110,7 @@
     client.name = name.isEmpty ? client.defaultClientName : name;
     clientResumePermissions.putIfAbsent(
       client.name!,
-      () => _ClientResumePermissions(),
+      () => ClientResumePermissions(),
     );
     clientResumePermissions[client.name!]!.clients.add(client);
   }
@@ -171,7 +171,7 @@
 
   /// Mapping of client names to all clients of that name and their resume
   /// permissions.
-  final Map<String?, _ClientResumePermissions> clientResumePermissions = {};
+  final Map<String?, ClientResumePermissions> clientResumePermissions = {};
 
   final DartDevelopmentServiceImpl dds;
 }
diff --git a/dds/lib/src/common/ring_buffer.dart b/dds/lib/src/common/ring_buffer.dart
index a2efa53..f970d41 100644
--- a/dds/lib/src/common/ring_buffer.dart
+++ b/dds/lib/src/common/ring_buffer.dart
@@ -22,7 +22,7 @@
   ///
   /// Returns the element evicted as a result of adding the new element if the
   /// buffer is as max capacity, null otherwise.
-  T? add(T e) {
+  T? add(T element) {
     if (_buffer.isEmpty) {
       return null;
     }
@@ -31,7 +31,7 @@
     if (index < _count) {
       evicted = _buffer[index];
     }
-    _buffer[index] = e;
+    _buffer[index] = element;
     _count++;
     return evicted;
   }
diff --git a/dds/lib/src/cpu_samples_manager.dart b/dds/lib/src/cpu_samples_manager.dart
index 71833fb..f4a2a92 100644
--- a/dds/lib/src/cpu_samples_manager.dart
+++ b/dds/lib/src/cpu_samples_manager.dart
@@ -119,8 +119,8 @@
   }
 
   @override
-  CpuSample? add(CpuSample sample) {
-    final evicted = super.add(sample);
+  CpuSample? add(CpuSample element) {
+    final evicted = super.add(element);
 
     void updateTicksForSample(CpuSample sample, int increment) {
       final stack = sample.stack!;
@@ -136,16 +136,16 @@
     if (evicted != null) {
       // If a sample is evicted from the cache, we need to decrement the tick
       // counters for each function in the sample's stack.
-      updateTicksForSample(sample, -1);
+      updateTicksForSample(element, -1);
 
       // We also need to change the first timestamp to that of the next oldest
       // sample.
       _firstSampleTimestamp = call().first.timestamp!;
     }
-    _lastSampleTimestamp = sample.timestamp!;
+    _lastSampleTimestamp = element.timestamp!;
 
     // Update function ticks to include the new sample.
-    updateTicksForSample(sample, 1);
+    updateTicksForSample(element, 1);
 
     return evicted;
   }
diff --git a/dds/lib/src/dap/adapters/dart.dart b/dds/lib/src/dap/adapters/dart.dart
index 9a03adc..cc901c1 100644
--- a/dds/lib/src/dap/adapters/dart.dart
+++ b/dds/lib/src/dap/adapters/dart.dart
@@ -7,7 +7,7 @@
 import 'dart:io';
 
 import 'package:collection/collection.dart';
-import 'package:json_rpc_2/error_code.dart' as jsonRpcErrors;
+import 'package:json_rpc_2/error_code.dart' as json_rpc_errors;
 import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 import 'package:vm_service/vm_service.dart' as vm;
@@ -317,7 +317,8 @@
 
   /// Manages VM Isolates and their events, including fanning out any requests
   /// to set breakpoints etc. from the client to all Isolates.
-  late IsolateManager _isolateManager;
+  @visibleForTesting
+  late IsolateManager isolateManager;
 
   /// A helper that handlers converting to/from DAP and VM Service types.
   late ProtocolConverter _converter;
@@ -369,9 +370,9 @@
   final Logger? logger;
 
   /// Whether the current debug session is an attach request (as opposed to a
-  /// launch request). Not available until after launchRequest or attachRequest
-  /// have been called.
-  late final bool isAttach;
+  /// launch request). Only set during [attachRequest] so will always be `false`
+  /// prior to that.
+  bool isAttach = false;
 
   /// A list of evaluateNames for InstanceRef IDs.
   ///
@@ -459,11 +460,11 @@
   Future<void> preventBreakingAndResume() async {
     // Remove anything that may cause us to pause again.
     await Future.wait([
-      _isolateManager.clearAllBreakpoints(),
-      _isolateManager.setExceptionPauseMode('None'),
+      isolateManager.clearAllBreakpoints(),
+      isolateManager.setExceptionPauseMode('None'),
     ]);
     // Once those have completed, it's safe to resume anything paused.
-    await _isolateManager.resumeAll();
+    await isolateManager.resumeAll();
   }
 
   DartDebugAdapter(
@@ -480,7 +481,7 @@
     dartSdkRoot = path.dirname(path.dirname(vmPath));
     orgDartlangSdkMappings[dartSdkRoot] = Uri.parse('org-dartlang-sdk:///sdk');
 
-    _isolateManager = IsolateManager(this);
+    isolateManager = IsolateManager(this);
     _converter = ProtocolConverter(this);
   }
 
@@ -542,7 +543,7 @@
 
     // When attaching to a process, suppress auto-resuming isolates until the
     // first time the user resumes anything.
-    _isolateManager.autoResumeStartingIsolates = false;
+    isolateManager.autoResumeStartingIsolates = false;
 
     // Common setup.
     await _prepareForLaunchOrAttach(null);
@@ -745,7 +746,7 @@
           ? vm.EventKind.kIsolateRunnable
           : vm.EventKind.kIsolateStart;
       final thread =
-          await _isolateManager.registerIsolate(isolate, pauseEventKind);
+          await isolateManager.registerIsolate(isolate, pauseEventKind);
 
       // If the Isolate already has a Pause event we can give it to the
       // IsolateManager to handle (if it's PausePostStart it will re-configure
@@ -753,16 +754,16 @@
       // runnable - otherwise we'll handle this when it becomes runnable in an
       // event later).
       if (isolate.pauseEvent?.kind?.startsWith('Pause') ?? false) {
-        await _isolateManager.handleEvent(
+        await isolateManager.handleEvent(
           isolate.pauseEvent!,
         );
       } else if (isolate.runnable == true) {
         // If requested, automatically resume. Otherwise send a Stopped event to
         // inform the client UI the thread is paused.
-        if (_isolateManager.autoResumeStartingIsolates) {
-          await _isolateManager.resumeIsolate(isolate);
+        if (isolateManager.autoResumeStartingIsolates) {
+          await isolateManager.resumeIsolate(isolate);
         } else {
-          _isolateManager.sendStoppedOnEntryEvent(thread.threadId);
+          isolateManager.sendStoppedOnEntryEvent(thread.threadId);
         }
       }
     }));
@@ -776,7 +777,7 @@
     ContinueArguments args,
     void Function(ContinueResponseBody) sendResponse,
   ) async {
-    await _isolateManager.resumeThread(args.threadId);
+    await isolateManager.resumeThread(args.threadId);
     sendResponse(ContinueResponseBody(allThreadsContinued: false));
   }
 
@@ -839,7 +840,7 @@
       // through the run daemon) as it needs to perform additional work to
       // rebuild widgets afterwards.
       case 'hotReload':
-        await _isolateManager.reloadSources();
+        await isolateManager.reloadSources();
         sendResponse(_noResult);
         break;
 
@@ -919,7 +920,7 @@
     ThreadInfo? thread;
     int? frameIndex;
     if (frameId != null) {
-      final data = _isolateManager.getStoredData(frameId);
+      final data = isolateManager.getStoredData(frameId);
       if (data != null) {
         thread = data.thread;
         frameIndex = (data.data as vm.Frame).index;
@@ -1182,9 +1183,9 @@
   /// by the `updateDebugOptions` custom request.
   Future<bool> libraryIsDebuggable(ThreadInfo thread, Uri uri) async {
     if (isSdkLibrary(uri)) {
-      return _isolateManager.debugSdkLibraries;
+      return isolateManager.debugSdkLibraries;
     } else if (await isExternalPackageLibrary(thread, uri)) {
-      return _isolateManager.debugExternalPackageLibraries;
+      return isolateManager.debugExternalPackageLibraries;
     } else {
       return true;
     }
@@ -1198,7 +1199,7 @@
     NextArguments args,
     void Function() sendResponse,
   ) async {
-    await _isolateManager.resumeThread(args.threadId, vm.StepOption.kOver);
+    await isolateManager.resumeThread(args.threadId, vm.StepOption.kOver);
     sendResponse();
   }
 
@@ -1227,27 +1228,43 @@
     ScopesArguments args,
     void Function(ScopesResponseBody) sendResponse,
   ) async {
+    final storedData = isolateManager.getStoredData(args.frameId);
+    final thread = storedData?.thread;
+    final data = storedData?.data;
+    final frameData = data is vm.Frame ? data : null;
     final scopes = <Scope>[];
 
-    // For local variables, we can just reuse the frameId as variablesReference
-    // as variablesRequest handles stored data of type `Frame` directly.
-    scopes.add(Scope(
-      name: 'Locals',
-      presentationHint: 'locals',
-      variablesReference: args.frameId,
-      expensive: false,
-    ));
-
-    // If the top frame has an exception, add an additional section to allow
-    // that to be inspected.
-    final data = _isolateManager.getStoredData(args.frameId);
-    final exceptionReference = data?.thread.exceptionReference;
-    if (exceptionReference != null) {
+    if (frameData != null && thread != null) {
       scopes.add(Scope(
-        name: 'Exceptions',
-        variablesReference: exceptionReference,
+        name: 'Locals',
+        presentationHint: 'locals',
+        variablesReference: isolateManager.storeData(
+          thread,
+          FrameScopeData(frameData, FrameScopeDataKind.locals),
+        ),
         expensive: false,
       ));
+
+      scopes.add(Scope(
+        name: 'Globals',
+        presentationHint: 'globals',
+        variablesReference: isolateManager.storeData(
+          thread,
+          FrameScopeData(frameData, FrameScopeDataKind.globals),
+        ),
+        expensive: false,
+      ));
+
+      // If the top frame has an exception, add an additional section to allow
+      // that to be inspected.
+      final exceptionReference = thread.exceptionReference;
+      if (exceptionReference != null) {
+        scopes.add(Scope(
+          name: 'Exceptions',
+          variablesReference: exceptionReference,
+          expensive: false,
+        ));
+      }
     }
 
     sendResponse(ScopesResponseBody(scopes: scopes));
@@ -1263,19 +1280,27 @@
   /// processing stack frames requires async calls, this function will insert
   /// output events into a queue and only send them when previous calls have
   /// been completed.
-  void sendOutput(String category, String message) async {
+  void sendOutput(
+    String category,
+    String message, {
+    int? variablesReference,
+  }) async {
     // Reserve our place in the queue be inserting a future that we can complete
     // after we have sent the output event.
     final completer = Completer<void>();
-    final _previousEvent = _lastOutputEvent ?? Future.value();
+    final previousEvent = _lastOutputEvent ?? Future.value();
     _lastOutputEvent = completer.future;
 
     try {
-      final outputEvents = await _buildOutputEvents(category, message);
+      final outputEvents = await _buildOutputEvents(
+        category,
+        message,
+        variablesReference: variablesReference,
+      );
 
       // Chain our sends onto the end of the previous one, and complete our Future
       // once done so that the next one can go.
-      await _previousEvent;
+      await previousEvent;
       outputEvents.forEach(sendEvent);
     } finally {
       completer.complete();
@@ -1301,7 +1326,7 @@
   /// the file URI in [args.source.path].
   ///
   /// The VM requires breakpoints to be set per-isolate so these will be passed
-  /// to [_isolateManager] that will fan them out to each isolate.
+  /// to [isolateManager] that will fan them out to each isolate.
   ///
   /// When new isolates are registered, it is [isolateManager]'s responsibility
   /// to ensure all breakpoints are given to them (and like at startup, this
@@ -1318,13 +1343,24 @@
     final name = args.source.name;
     final uri = path != null ? Uri.file(normalizePath(path)).toString() : name!;
 
-    await _isolateManager.setBreakpoints(uri, breakpoints);
+    // Use a completer to track when the response is sent, so any events related
+    // to these breakpoints are not sent before the client has the IDs.
+    final completer = Completer<void>();
 
-    // TODO(dantup): Handle breakpoint resolution rather than pretending all
-    // breakpoints are verified immediately.
+    final clientBreakpoints = breakpoints
+        .map((bp) => ClientBreakpoint(bp, completer.future))
+        .toList();
+    await isolateManager.setBreakpoints(uri, clientBreakpoints);
+
     sendResponse(SetBreakpointsResponseBody(
-      breakpoints: breakpoints.map((e) => Breakpoint(verified: true)).toList(),
+      breakpoints: clientBreakpoints
+          // Send breakpoints back as unverified and with our generated IDs so we
+          // can update them with a 'breakpoint' event when we get the
+          // 'BreakpointAdded'/'BreakpointResolved' events from the VM.
+          .map((bp) => Breakpoint(id: bp.id, verified: false))
+          .toList(),
     ));
+    completer.complete();
   }
 
   /// Handles a request from the client to set exception pause modes.
@@ -1333,7 +1369,7 @@
   /// the app is running).
   ///
   /// The VM requires exception modes to be set per-isolate so these will be
-  /// passed to [_isolateManager] that will fan them out to each isolate.
+  /// passed to [isolateManager] that will fan them out to each isolate.
   ///
   /// When new isolates are registered, it is [isolateManager]'s responsibility
   /// to ensure the pause mode is given to them (and like at startup, this
@@ -1350,7 +1386,7 @@
             ? 'Unhandled'
             : 'None';
 
-    await _isolateManager.setExceptionPauseMode(mode);
+    await isolateManager.setExceptionPauseMode(mode);
 
     sendResponse(SetExceptionBreakpointsResponseBody());
   }
@@ -1370,6 +1406,7 @@
 
   /// Shuts down the debug adapter, including terminating/detaching from the
   /// debugee if required.
+  @override
   @nonVirtual
   Future<void> shutdown() async {
     await shutdownDebugee();
@@ -1448,7 +1485,7 @@
     SourceArguments args,
     void Function(SourceResponseBody) sendResponse,
   ) async {
-    final storedData = _isolateManager.getStoredData(
+    final storedData = isolateManager.getStoredData(
       args.source?.sourceReference ?? args.sourceReference,
     );
     if (storedData == null) {
@@ -1499,7 +1536,7 @@
     const stackFrameBatchSize = 20;
 
     final threadId = args.threadId;
-    final thread = _isolateManager.getThread(threadId);
+    final thread = isolateManager.getThread(threadId);
     final topFrame = thread?.pauseEvent?.topFrame;
     final startFrame = args.startFrame ?? 0;
     final numFrames = args.levels ?? 0;
@@ -1596,7 +1633,7 @@
     StepInArguments args,
     void Function() sendResponse,
   ) async {
-    await _isolateManager.resumeThread(args.threadId, vm.StepOption.kInto);
+    await isolateManager.resumeThread(args.threadId, vm.StepOption.kInto);
     sendResponse();
   }
 
@@ -1607,7 +1644,7 @@
     StepOutArguments args,
     void Function() sendResponse,
   ) async {
-    await _isolateManager.resumeThread(args.threadId, vm.StepOption.kOut);
+    await isolateManager.resumeThread(args.threadId, vm.StepOption.kOut);
     sendResponse();
   }
 
@@ -1658,7 +1695,7 @@
     void Function(ThreadsResponseBody) sendResponse,
   ) async {
     final threads = [
-      for (final thread in _isolateManager.threads)
+      for (final thread in isolateManager.threads)
         Thread(
           id: thread.threadId,
           name: thread.isolate.name ?? '<unnamed isolate>',
@@ -1687,7 +1724,7 @@
   ) async {
     final childStart = args.start;
     final childCount = args.count;
-    final storedData = _isolateManager.getStoredData(args.variablesReference);
+    final storedData = isolateManager.getStoredData(args.variablesReference);
     if (storedData == null) {
       throw StateError('variablesReference is no longer valid');
     }
@@ -1706,8 +1743,8 @@
 
     final variables = <Variable>[];
 
-    if (data is vm.Frame) {
-      final vars = data.vars;
+    if (data is FrameScopeData && data.kind == FrameScopeDataKind.locals) {
+      final vars = data.frame.vars;
       if (vars != null) {
         Future<Variable> convert(int index, vm.BoundVariable variable) {
           // Store the expression that gets this object as we may need it to
@@ -1729,6 +1766,32 @@
         // Sort the variables by name.
         variables.sortBy((v) => v.name);
       }
+    } else if (data is FrameScopeData &&
+        data.kind == FrameScopeDataKind.globals) {
+      /// Helper to simplify calling converter.
+      Future<Variable> convert(int index, vm.FieldRef fieldRef) async {
+        return _converter.convertFieldRefToVariable(
+          thread,
+          fieldRef,
+          allowCallingToString: evaluateToStringInDebugViews &&
+              index <= maxToStringsPerEvaluation,
+          format: format,
+        );
+      }
+
+      final globals = await _getFrameGlobals(thread, data.frame);
+      variables.addAll(await Future.wait(globals.mapIndexed(convert)));
+      variables.sortBy((v) => v.name);
+    } else if (data is InspectData) {
+      // When sending variables as part of an OutputEvent, VS Code will only
+      // show the first field, so we wrap the object to ensure there's always
+      // a single field.
+      final instance = data.instance;
+      variables.add(Variable(
+        name: '', // Unused.
+        value: '<inspected variable>', // Shown to user, expandable.
+        variablesReference: instance != null ? thread.storeData(instance) : 0,
+      ));
     } else if (data is vm.MapAssociation) {
       final key = data.key;
       final value = data.value;
@@ -1764,7 +1827,7 @@
         ]);
       }
     } else if (data is vm.ObjRef) {
-      final object = await _isolateManager.getObject(
+      final object = await isolateManager.getObject(
         storedData.thread.isolate,
         data,
         offset: childStart,
@@ -1799,6 +1862,30 @@
     sendResponse(VariablesResponseBody(variables: variables));
   }
 
+  /// Gets global variables for the library of [frame].
+  Future<List<vm.FieldRef>> _getFrameGlobals(
+    ThreadInfo thread,
+    vm.Frame frame,
+  ) async {
+    final scriptRef = frame.location?.script;
+    if (scriptRef == null) {
+      return [];
+    }
+
+    final script = await thread.getScript(scriptRef);
+    final libraryRef = script.library;
+    if (libraryRef == null) {
+      return [];
+    }
+
+    final library = await thread.getObject(libraryRef);
+    if (library is! vm.Library) {
+      return [];
+    }
+
+    return library.variables ?? [];
+  }
+
   /// Fixes up a VM Service WebSocket URI to not have a trailing /ws
   /// and use the HTTP scheme which is what DDS expects.
   Uri vmServiceUriToHttp(Uri uri) {
@@ -1838,13 +1925,20 @@
   /// for each frame to allow location metadata to be attached.
   Future<List<OutputEventBody>> _buildOutputEvents(
     String category,
-    String message,
-  ) async {
+    String message, {
+    int? variablesReference,
+  }) async {
     try {
       if (category == 'stderr') {
         return await _buildStdErrOutputEvents(message);
       } else {
-        return [OutputEventBody(category: category, output: message)];
+        return [
+          OutputEventBody(
+            category: category,
+            output: message,
+            variablesReference: variablesReference,
+          )
+        ];
       }
     } catch (e, s) {
       // Since callers of [sendOutput] may not await it, don't allow unhandled
@@ -1871,7 +1965,7 @@
     // isolate printed an error to stderr, we just have to use the first one and
     // hope the packages are available. If one is not available (which should
     // never be the case), we will just skip resolution.
-    final thread = _isolateManager.threads.firstOrNull;
+    final thread = isolateManager.threads.firstOrNull;
 
     // Send a batch request. This will cache the results so we can easily use
     // them in the loop below by calling the method again.
@@ -1942,7 +2036,7 @@
     String expression,
     ThreadInfo thread,
   ) async {
-    final exception = _isolateManager.getStoredData(exceptionReference)?.data
+    final exception = isolateManager.getStoredData(exceptionReference)?.data
         as vm.InstanceRef?;
 
     if (exception == null) {
@@ -1974,7 +2068,7 @@
     // it's doing is own initialization that this may interfere with.
     await debuggerInitialized;
 
-    await _isolateManager.handleEvent(event);
+    await isolateManager.handleEvent(event);
 
     final eventKind = event.kind;
     final isolate = event.isolate;
@@ -1985,7 +2079,7 @@
         eventKind == vm.EventKind.kPauseExit &&
         isolate != null) {
       await _waitForPendingOutputEvents();
-      await _isolateManager.resumeIsolate(isolate);
+      await isolateManager.resumeIsolate(isolate);
     }
   }
 
@@ -2007,7 +2101,7 @@
     await debuggerInitialized;
 
     // Allow IsolateManager to handle any state-related events.
-    await _isolateManager.handleEvent(event);
+    await isolateManager.handleEvent(event);
 
     switch (event.kind) {
       // Pass any Service Extension events on to the client so they can enable
@@ -2057,7 +2151,7 @@
   @mustCallSuper
   Future<void> handleLoggingEvent(vm.Event event) async {
     final record = event.logRecord;
-    final thread = _isolateManager.threadForIsolate(event.isolate);
+    final thread = isolateManager.threadForIsolate(event.isolate);
     if (record == null || thread == null) {
       return;
     }
@@ -2110,7 +2204,7 @@
     Map<String, Object?> data,
     String field,
   ) async {
-    final thread = _isolateManager.threadForIsolate(isolate);
+    final thread = isolateManager.threadForIsolate(isolate);
     if (thread == null) {
       return;
     }
@@ -2197,9 +2291,9 @@
     // Notify IsolateManager if we'll be debugging so it knows whether to set
     // up breakpoints etc. when isolates are registered.
     final debug = !(noDebug ?? false);
-    _isolateManager.debug = debug;
-    _isolateManager.debugSdkLibraries = args.debugSdkLibraries ?? true;
-    _isolateManager.debugExternalPackageLibraries =
+    isolateManager.debug = debug;
+    isolateManager.debugSdkLibraries = args.debugSdkLibraries ?? true;
+    isolateManager.debugExternalPackageLibraries =
         args.debugExternalPackageLibraries ?? true;
   }
 
@@ -2242,13 +2336,13 @@
   /// in the map will not be updated by this method.
   Future<void> _updateDebugOptions(Map<String, Object?> args) async {
     if (args.containsKey('debugSdkLibraries')) {
-      _isolateManager.debugSdkLibraries = args['debugSdkLibraries'] as bool;
+      isolateManager.debugSdkLibraries = args['debugSdkLibraries'] as bool;
     }
     if (args.containsKey('debugExternalPackageLibraries')) {
-      _isolateManager.debugExternalPackageLibraries =
+      isolateManager.debugExternalPackageLibraries =
           args['debugExternalPackageLibraries'] as bool;
     }
-    await _isolateManager.applyDebugOptions();
+    await isolateManager.applyDebugOptions();
   }
 
   /// A wrapper around the same name function from package:vm_service that
@@ -2327,7 +2421,7 @@
         }
         // SERVER_ERROR can occur when DDS completes any outstanding requests
         // with "The client closed with pending request".
-        if (e.code == jsonRpcErrors.SERVER_ERROR) {
+        if (e.code == json_rpc_errors.SERVER_ERROR) {
           return null;
         }
       }
@@ -2355,6 +2449,7 @@
 
   /// If noDebug is true the launch request should launch the program without
   /// enabling debugging.
+  @override
   final bool? noDebug;
 
   /// The program/Dart script to be run.
diff --git a/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index 0607f98..0bbbeae 100644
--- a/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -49,8 +49,10 @@
   ///
   /// If we have a process, we will instead use its termination as a signal to
   /// terminate the debug session. Otherwise, we will use the VM Service close.
+  @override
   bool get terminateOnVmServiceClose => _process == null;
 
+  @override
   Future<void> debuggerConnected(vm.VM vmInfo) async {
     if (!isAttach) {
       // Capture the PID from the VM Service so that we can terminate it when
@@ -67,6 +69,7 @@
 
   /// Called by [disconnectRequest] to request that we forcefully shut down the
   /// app being run (or in the case of an attach, disconnect).
+  @override
   Future<void> disconnectImpl() async {
     if (isAttach) {
       await handleDetach();
@@ -87,6 +90,7 @@
   ///
   /// For debugging, this should start paused, connect to the VM Service, set
   /// breakpoints, and resume.
+  @override
   Future<void> launchImpl() async {
     final args = this.args as DartLaunchRequestArguments;
     File? vmServiceInfoFile;
@@ -191,6 +195,7 @@
 
   /// Called by [attachRequest] to request that we actually connect to the app
   /// to be debugged.
+  @override
   Future<void> attachImpl() async {
     final args = this.args as DartAttachRequestArguments;
     final vmServiceUri = args.vmServiceUri;
@@ -224,7 +229,7 @@
   }) async {
     final args = this.args as DartLaunchRequestArguments;
     logger?.call('Spawning $executable with $processArgs in $workingDirectory'
-        ' via client ${terminalKind} terminal');
+        ' via client $terminalKind terminal');
 
     // runInTerminal is a DAP request that goes from server-to-client that
     // allows the DA to ask the client editor to run the debugee for us. In this
@@ -287,6 +292,7 @@
 
   /// Called by [terminateRequest] to request that we gracefully shut down the
   /// app being run (or in the case of an attach, disconnect).
+  @override
   Future<void> terminateImpl() async {
     if (isAttach) {
       await handleDetach();
diff --git a/dds/lib/src/dap/adapters/dart_test_adapter.dart b/dds/lib/src/dap/adapters/dart_test_adapter.dart
index 6fc3ff6..4d4685c 100644
--- a/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -47,8 +47,10 @@
   /// debug session.
   ///
   /// Since we do not support attaching for tests, this is always false.
+  @override
   bool get terminateOnVmServiceClose => false;
 
+  @override
   Future<void> debuggerConnected(vm.VM vmInfo) async {
     // Capture the PID from the VM Service so that we can terminate it when
     // cleaning up. Terminating the process might not be enough as it could be
@@ -63,6 +65,7 @@
 
   /// Called by [disconnectRequest] to request that we forcefully shut down the
   /// app being run (or in the case of an attach, disconnect).
+  @override
   Future<void> disconnectImpl() async {
     terminatePids(ProcessSignal.sigkill);
   }
@@ -72,6 +75,7 @@
   ///
   /// For debugging, this should start paused, connect to the VM Service, set
   /// breakpoints, and resume.
+  @override
   Future<void> launchImpl() async {
     final args = this.args as DartLaunchRequestArguments;
     File? vmServiceInfoFile;
@@ -163,6 +167,7 @@
 
   /// Called by [attachRequest] to request that we actually connect to the app
   /// to be debugged.
+  @override
   Future<void> attachImpl() async {
     sendOutput('console', '\nAttach is not supported for test runs');
     handleSessionTerminate();
@@ -170,8 +175,18 @@
 
   /// Called by [terminateRequest] to request that we gracefully shut down the
   /// app being run (or in the case of an attach, disconnect).
+  @override
   Future<void> terminateImpl() async {
     terminatePids(ProcessSignal.sigterm);
+
+    // Sending a kill signal to pkg:test doesn't cause it to exit immediately,
+    // instead it waits for the current test to complete. If the user is at
+    // a breakpoint this will never happen, which will encourage them to click
+    // Stop again. In VS Code, a second Stop is a non-graceful shutdown which
+    // may leave orphaned processes. To avoid this, attempt to resume and avoid
+    // any further pausing.
+    await preventBreakingAndResume();
+
     await _process?.exitCode;
   }
 
diff --git a/dds/lib/src/dap/adapters/mixins.dart b/dds/lib/src/dap/adapters/mixins.dart
index c7cbea8..3fb3b12 100644
--- a/dds/lib/src/dap/adapters/mixins.dart
+++ b/dds/lib/src/dap/adapters/mixins.dart
@@ -41,9 +41,9 @@
   void terminatePids(ProcessSignal signal) {
     // TODO(dantup): In Dart-Code DAP, we first try again with sigint and wait
     // for a few seconds before sending sigkill.
-    pidsToTerminate.forEach(
-      (pid) => Process.killPid(pid, signal),
-    );
+    for (var pid in pidsToTerminate) {
+      Process.killPid(pid, signal);
+    }
   }
 }
 
@@ -57,10 +57,10 @@
   /// Test names by testID.
   ///
   /// Stored in testStart so that they can be looked up in testDone.
-  Map<int, String> _testNames = {};
+  final Map<int, String> _testNames = {};
 
   void sendEvent(EventBody body, {String? eventType});
-  void sendOutput(String category, String message);
+  void sendOutput(String category, String message, {int? variablesReference});
 
   void sendTestEvents(Object testNotification) {
     // Send the JSON package as a raw notification so the client can interpret
diff --git a/dds/lib/src/dap/base_debug_adapter.dart b/dds/lib/src/dap/base_debug_adapter.dart
index 32f1e72..e20e494 100644
--- a/dds/lib/src/dap/base_debug_adapter.dart
+++ b/dds/lib/src/dap/base_debug_adapter.dart
@@ -13,7 +13,7 @@
 
 typedef _FromJsonHandler<T> = T Function(Map<String, Object?>);
 typedef _NullableFromJsonHandler<T> = T? Function(Map<String, Object?>?);
-typedef _RequestHandler<TArg, TResp> = Future<void> Function(
+typedef RequestHandler<TArg, TResp> = Future<void> Function(
     Request, TArg, void Function(TResp));
 typedef _VoidArgRequestHandler<TArg> = Future<void> Function(
     Request, TArg, void Function(void));
@@ -105,7 +105,7 @@
   /// If [handler] throws, its exception will be sent as an error response.
   Future<void> handle<TArg, TResp>(
     Request request,
-    _RequestHandler<TArg, TResp> handler,
+    RequestHandler<TArg, TResp> handler,
     TArg Function(Map<String, Object?>) fromJson,
   ) async {
     try {
diff --git a/dds/lib/src/dap/exceptions.dart b/dds/lib/src/dap/exceptions.dart
index 81431db..4523a8d 100644
--- a/dds/lib/src/dap/exceptions.dart
+++ b/dds/lib/src/dap/exceptions.dart
@@ -10,6 +10,7 @@
 
   DebugAdapterException(this.message);
 
+  @override
   String toString() => 'DebugAdapterException: $message';
 }
 
@@ -49,5 +50,6 @@
       '"$argumentName" argument in $requestName configuration must be a '
       '$expectedType but provided value was a $actualType ($actualValue)';
 
+  @override
   String toString() => 'DebugAdapterInvalidArgumentException: $message';
 }
diff --git a/dds/lib/src/dap/isolate_manager.dart b/dds/lib/src/dap/isolate_manager.dart
index bf3fe1d..77a8918 100644
--- a/dds/lib/src/dap/isolate_manager.dart
+++ b/dds/lib/src/dap/isolate_manager.dart
@@ -13,6 +13,7 @@
 import 'exceptions.dart';
 import 'protocol_generated.dart';
 import 'utils.dart';
+import 'variables.dart';
 
 /// Manages state of Isolates (called Threads by the DAP protocol).
 ///
@@ -67,11 +68,11 @@
 
   /// Tracks breakpoints last provided by the client so they can be sent to new
   /// isolates that appear after initial breakpoints were sent.
-  final Map<String, List<SourceBreakpoint>> _clientBreakpointsByUri = {};
+  final Map<String, List<ClientBreakpoint>> _clientBreakpointsByUri = {};
 
   /// Tracks client breakpoints by the ID assigned by the VM so we can look up
   /// conditions/logpoints when hitting breakpoints.
-  final Map<String, SourceBreakpoint> _clientBreakpointsByVmId = {};
+  final Map<String, ClientBreakpoint> _clientBreakpointsByVmId = {};
 
   /// Tracks breakpoints created in the VM so they can be removed when the
   /// editor sends new breakpoints (currently the editor just sends a new list
@@ -102,7 +103,7 @@
   ///
   /// Stored data is thread-scoped but the client will not provide the thread
   /// when asking for data so it's all stored together here.
-  final _storedData = <int, _StoredData>{};
+  final _storedData = <int, StoredData>{};
 
   /// A pattern that matches an opening brace `{` that was not preceded by a
   /// dollar.
@@ -148,7 +149,7 @@
 
   /// Retrieves some basic data indexed by an integer for use in "reference"
   /// fields that are round-tripped to the client.
-  _StoredData? getStoredData(int id) {
+  StoredData? getStoredData(int id) {
     return _storedData[id];
   }
 
@@ -177,6 +178,11 @@
       await _handlePause(event);
     } else if (eventKind == vm.EventKind.kResume) {
       _handleResumed(event);
+    } else if (eventKind == vm.EventKind.kInspect) {
+      _handleInspect(event);
+    } else if (eventKind == vm.EventKind.kBreakpointAdded ||
+        eventKind == vm.EventKind.kBreakpointResolved) {
+      _handleBreakpointAddedOrResolved(event);
     }
   }
 
@@ -289,7 +295,7 @@
   /// before.
   Future<void> setBreakpoints(
     String uri,
-    List<SourceBreakpoint> breakpoints,
+    List<ClientBreakpoint> breakpoints,
   ) async {
     // Track the breakpoints to get sent to any new isolates that start.
     _clientBreakpointsByUri[uri] = breakpoints;
@@ -326,7 +332,7 @@
   /// that are round-tripped to the client.
   int storeData(ThreadInfo thread, Object data) {
     final id = _nextStoredDataId++;
-    _storedData[id] = _StoredData(thread, data);
+    _storedData[id] = StoredData(thread, data);
     return id;
   }
 
@@ -479,7 +485,7 @@
         // we hit. It's possible some of these may be missing because we could
         // hit a breakpoint that was set before we were attached.
         final clientBreakpoints = event.pauseBreakpoints!
-            .map((bp) => _clientBreakpointsByVmId[bp.id!])
+            .map((bp) => _clientBreakpointsByVmId[bp.id!]?.breakpoint)
             .toSet();
 
         // Split into logpoints (which just print messages) and breakpoints.
@@ -536,6 +542,59 @@
     }
   }
 
+  /// Handles an inspect event from the VM, sending the value/variable to the
+  /// debugger.
+  void _handleInspect(vm.Event event) {
+    final isolate = event.isolate!;
+    final thread = _threadsByIsolateId[isolate.id!];
+    final inspectee = event.inspectee;
+
+    if (thread != null && inspectee != null) {
+      final ref = thread.storeData(InspectData(inspectee));
+      _adapter.sendOutput(
+        'console',
+        '', // Not shown by the client because it fetches the variable.
+        variablesReference: ref,
+      );
+    }
+  }
+
+  /// Handles 'BreakpointAdded'/'BreakpointResolved' events from the VM,
+  /// informing the client of updated information about the breakpoint.
+  void _handleBreakpointAddedOrResolved(vm.Event event) {
+    final breakpoint = event.breakpoint!;
+    final breakpointId = breakpoint.id!;
+
+    final existingBreakpoint = _clientBreakpointsByVmId[breakpointId];
+    if (existingBreakpoint == null) {
+      // If we can't match this breakpoint up, we cannot get its ID or send
+      // events for it. This can happen if a breakpoint is being resolved just
+      // as a user changes breakpoints, so we have replaced our collection with
+      // a new set before we processed this event.
+      return;
+    }
+
+    // Location may be [SourceLocation] or [UnresolvedSourceLocaion] depending
+    // on whether this is an Added or Resolved event.
+    final location = breakpoint.location;
+    final resolvedLocation = location is vm.SourceLocation ? location : null;
+    final unresolvedLocation =
+        location is vm.UnresolvedSourceLocation ? location : null;
+    final updatedBreakpoint = Breakpoint(
+      id: existingBreakpoint.id,
+      line: resolvedLocation?.line ?? unresolvedLocation?.line,
+      column: resolvedLocation?.column ?? unresolvedLocation?.column,
+      verified: breakpoint.resolved ?? false,
+    );
+    // Ensure we don't send the breakpoint event until the client has been
+    // given the breakpoint ID.
+    existingBreakpoint.queueAction(
+      () => _adapter.sendEvent(
+        BreakpointEventBody(breakpoint: updatedBreakpoint, reason: 'changed'),
+      ),
+    );
+  }
+
   /// Attempts to resolve [uris] to file:/// URIs via the VM Service.
   ///
   /// This method calls the VM service directly. Most requests to resolve URIs
@@ -655,7 +714,7 @@
 
       // Set new breakpoints.
       final newBreakpoints = _clientBreakpointsByUri[uri] ?? const [];
-      await Future.forEach<SourceBreakpoint>(newBreakpoints, (bp) async {
+      await Future.forEach<ClientBreakpoint>(newBreakpoints, (bp) async {
         try {
           // Some file URIs (like SDK sources) need to be converted to
           // appropriate internal URIs to be able to set breakpoints.
@@ -668,8 +727,8 @@
           }
 
           final vmBp = await service.addBreakpointWithScriptUri(
-              isolateId, vmUri.toString(), bp.line,
-              column: bp.column);
+              isolateId, vmUri.toString(), bp.breakpoint.line,
+              column: bp.breakpoint.column);
           existingBreakpointsForIsolateAndUri[vmBp.id!] = vmBp;
           _clientBreakpointsByVmId[vmBp.id!] = bp;
         } catch (e) {
@@ -722,12 +781,18 @@
 
     await Future.wait(libraries.map((library) async {
       final libraryUri = library.uri;
-      final isDebuggable = libraryUri != null
+      final isDebuggableNew = libraryUri != null
           ? await _adapter.libraryIsDebuggable(thread, Uri.parse(libraryUri))
           : false;
+      final isDebuggableCurrent =
+          thread.getIsLibraryCurrentlyDebuggable(library);
+      thread.setIsLibraryCurrentlyDebuggable(library, isDebuggableNew);
+      if (isDebuggableNew == isDebuggableCurrent) {
+        return;
+      }
       try {
         await service.setLibraryDebuggable(
-            isolateId, library.id!, isDebuggable);
+            isolateId, library.id!, isDebuggableNew);
       } on vm.RPCError catch (e) {
         // DWDS does not currently support `setLibraryDebuggable` so instead of
         // failing (because this code runs in a VM event handler where there's
@@ -944,6 +1009,46 @@
     return Future.wait(futures);
   }
 
+  /// Returns whether [library] is currently debuggable according to the VM
+  /// (or there is a request in-flight to set it).
+  bool getIsLibraryCurrentlyDebuggable(vm.LibraryRef library) {
+    return _libraryIsDebuggableById[library.id!] ??
+        _getIsLibraryDebuggableByDefault(library);
+  }
+
+  /// Records whether [library] is currently debuggable for this isolate.
+  ///
+  /// This should be called whenever a `setLibraryDebuggable` request is made
+  /// to the VM.
+  void setIsLibraryCurrentlyDebuggable(
+    vm.LibraryRef library,
+    bool isDebuggable,
+  ) {
+    if (isDebuggable == _getIsLibraryDebuggableByDefault(library)) {
+      _libraryIsDebuggableById.remove(library.id!);
+    } else {
+      _libraryIsDebuggableById[library.id!] = isDebuggable;
+    }
+  }
+
+  /// Returns whether [library] is debuggable by default.
+  ///
+  /// This value is _assumed_ to avoid having to fetch each library for each
+  /// isolate.
+  bool _getIsLibraryDebuggableByDefault(vm.LibraryRef library) {
+    final isSdkLibrary = library.uri?.startsWith('dart:') ?? false;
+    return !isSdkLibrary;
+  }
+
+  /// Tracks whether libraries are currently marked as debuggable in the VM.
+  ///
+  /// If a library ID is not in the map, it is set to the default (which is
+  /// debuggable for non-SDK sources, and not-debuggable for SDK sources).
+  ///
+  /// This can be used to avoid calling setLibraryDebuggable where the value
+  /// would not be changed.
+  final _libraryIsDebuggableById = <String, bool>{};
+
   /// Resolves a source URI to a file path for the lib folder of its package.
   ///
   /// package:foo/a/b/c/d.dart -> /code/packages/foo/lib
@@ -1035,9 +1140,47 @@
   }
 }
 
-class _StoredData {
+/// A wrapper over the client-provided [SourceBreakpoint] with a unique ID.
+///
+/// In order to tell clients about breakpoint changes (such as resolution) we
+/// must assign them an ID. If the VM does not have any running Isolates at the
+/// time initial breakpoints are set we cannot yet send the breakpoints (and
+/// therefore cannot get IDs from the VM). So we generate our own IDs and hold
+/// them with the breakpoint here. When we get a 'BreakpointResolved' event we
+/// can look up this [ClientBreakpoint] and use the ID to send an update to the
+/// client.
+class ClientBreakpoint {
+  /// The next number to use as a client ID for breakpoints.
+  ///
+  /// To slightly improve debugging, we start this at 100000 so it doesn't
+  /// initially overlap with VM-produced breakpoint numbers so it's more obvious
+  /// in log files which numbers are DAP-client and which are VM.
+  static int _nextId = 100000;
+
+  final SourceBreakpoint breakpoint;
+  final int id;
+
+  /// A [Future] that completes with the last action that sends breakpoint
+  /// information to the client, to ensure breakpoint events are always sent
+  /// in-order and after the initial response sending the IDs to the client.
+  Future<void> _lastActionFuture;
+
+  ClientBreakpoint(this.breakpoint, Future<void> setBreakpointResponse)
+      : id = _nextId++,
+        _lastActionFuture = setBreakpointResponse;
+
+  /// Queues an action to run after all previous actions that sent breakpoint
+  /// information to the client.
+  FutureOr<T> queueAction<T>(FutureOr<T> Function() action) {
+    final actionFuture = _lastActionFuture.then((_) => action());
+    _lastActionFuture = actionFuture;
+    return actionFuture;
+  }
+}
+
+class StoredData {
   final ThreadInfo thread;
   final Object data;
 
-  _StoredData(this.thread, this.data);
+  StoredData(this.thread, this.data);
 }
diff --git a/dds/lib/src/dap/protocol_converter.dart b/dds/lib/src/dap/protocol_converter.dart
index 9728912..7bba783 100644
--- a/dds/lib/src/dap/protocol_converter.dart
+++ b/dds/lib/src/dap/protocol_converter.dart
@@ -11,7 +11,6 @@
 import 'package:vm_service/vm_service.dart' as vm;
 
 import '../../dap.dart';
-import 'adapters/dart.dart';
 import 'isolate_manager.dart';
 import 'protocol_generated.dart' as dap;
 import 'variables.dart';
@@ -148,17 +147,25 @@
       // For lists, map each item (in the requested subset) to a variable.
       final start = startItem ?? 0;
       return Future.wait(elements.cast<vm.Response>().mapIndexed(
-            (index, response) => convertVmResponseToVariable(
-              thread,
-              response,
-              name: '[${start + index}]',
-              evaluateName: _adapter.combineEvaluateName(
-                  evaluateName, '[${start + index}]'),
-              allowCallingToString:
-                  allowCallingToString && index <= maxToStringsPerEvaluation,
-              format: format,
-            ),
-          ));
+        (index, response) {
+          final name = '[${start + index}]';
+          final itemEvaluateName =
+              _adapter.combineEvaluateName(evaluateName, name);
+          if (response is vm.InstanceRef) {
+            _adapter.storeEvaluateName(response, itemEvaluateName);
+          }
+
+          return convertVmResponseToVariable(
+            thread,
+            response,
+            name: name,
+            evaluateName: itemEvaluateName,
+            allowCallingToString:
+                allowCallingToString && index <= maxToStringsPerEvaluation,
+            format: format,
+          );
+        },
+      ));
     } else if (associations != null) {
       // For maps, create a variable for each entry (in the requested subset).
       // Use the keys and values to create a display string in the form
@@ -206,15 +213,17 @@
       final elements = _decodeList(instance);
 
       final start = startItem ?? 0;
-      return elements
-          .mapIndexed(
-            (index, element) => dap.Variable(
-              name: '[${start + index}]',
-              value: element.toString(),
-              variablesReference: 0,
-            ),
-          )
-          .toList();
+      return elements.mapIndexed(
+        (index, element) {
+          final name = '[${start + index}]';
+          return dap.Variable(
+            name: name,
+            evaluateName: _adapter.combineEvaluateName(evaluateName, name),
+            value: element.toString(),
+            variablesReference: 0,
+          );
+        },
+      ).toList();
     } else if (fields != null) {
       // Otherwise, show the fields from the instance.
       final variables = await Future.wait(fields.mapIndexed(
@@ -227,13 +236,17 @@
           } else {
             name ??= field.name;
           }
+          final fieldEvaluateName = name != null
+              ? _adapter.combineEvaluateName(evaluateName, '.$name')
+              : null;
+          if (fieldEvaluateName != null) {
+            _adapter.storeEvaluateName(field.value, fieldEvaluateName);
+          }
           return convertVmResponseToVariable(
             thread,
             field.value,
             name: name ?? '<unnamed field>',
-            evaluateName: name != null
-                ? _adapter.combineEvaluateName(evaluateName, '.$name')
-                : null,
+            evaluateName: fieldEvaluateName,
             allowCallingToString:
                 allowCallingToString && index <= maxToStringsPerEvaluation,
             format: format,
@@ -258,6 +271,11 @@
               instance.id!,
               getterName,
             );
+            final fieldEvaluateName =
+                _adapter.combineEvaluateName(evaluateName, '.$getterName');
+            if (response is vm.InstanceRef) {
+              _adapter.storeEvaluateName(response, fieldEvaluateName);
+            }
             // Convert results to variables.
             return convertVmResponseToVariable(
               thread,
@@ -350,6 +368,11 @@
         allowCallingToString: allowCallingToString,
         format: format,
       );
+    } else if (response is vm.ErrorRef) {
+      final errorMessage = response.message;
+      return errorMessage != null
+          ? _adapter.extractUnhandledExceptionMessage(errorMessage)
+          : response.kind ?? '<unknown error>';
     } else if (response is vm.Sentinel) {
       return '<sentinel>';
     } else {
@@ -357,6 +380,31 @@
     }
   }
 
+  Future<dap.Variable> convertFieldRefToVariable(
+    ThreadInfo thread,
+    vm.FieldRef fieldRef, {
+    required bool allowCallingToString,
+    required VariableFormat? format,
+  }) async {
+    final field = await thread.getObject(fieldRef);
+    if (field is vm.Field) {
+      return convertVmResponseToVariable(
+        thread,
+        field.staticValue,
+        name: fieldRef.name,
+        allowCallingToString: allowCallingToString,
+        evaluateName: fieldRef.name,
+        format: format,
+      );
+    } else {
+      return Variable(
+        name: fieldRef.name ?? '<unnamed field>',
+        value: '<unavailable>',
+        variablesReference: 0,
+      );
+    }
+  }
+
   /// Converts a [vm.Response] into to a [dap.Variable].
   ///
   /// If provided, [name] is used as the variables name (for example the field
diff --git a/dds/lib/src/dap/protocol_generated.dart b/dds/lib/src/dap/protocol_generated.dart
index 1644eaa..eee6a00 100644
--- a/dds/lib/src/dap/protocol_generated.dart
+++ b/dds/lib/src/dap/protocol_generated.dart
@@ -2,7 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// This code was auto-generated by tool/dap/generate_all.dart - do not hand-edit!
+// This code was auto-generated by tool/dap/generate_all.dart; do not hand-edit!
+
+// ignore_for_file: prefer_void_to_null
 
 import 'protocol_common.dart';
 import 'protocol_special.dart';
diff --git a/dds/lib/src/dap/protocol_stream_transformers.dart b/dds/lib/src/dap/protocol_stream_transformers.dart
index 7d70edb..831202f 100644
--- a/dds/lib/src/dap/protocol_stream_transformers.dart
+++ b/dds/lib/src/dap/protocol_stream_transformers.dart
@@ -30,11 +30,11 @@
   @override
   Stream<String> bind(Stream<List<int>> stream) {
     late StreamSubscription<int> input;
-    late StreamController<String> _output;
+    late StreamController<String> output;
     final buffer = <int>[];
     var isParsingHeaders = true;
     ProtocolHeaders? headers;
-    _output = StreamController<String>(
+    output = StreamController<String>(
       onListen: () {
         input = stream.expand((b) => b).listen(
           (codeUnit) {
@@ -51,9 +51,9 @@
                 // Any other encodings should be rejected with an error.
                 if ([null, 'utf-8', 'utf8']
                     .contains(headers?.encoding?.toLowerCase())) {
-                  _output.add(utf8.decode(buffer));
+                  output.add(utf8.decode(buffer));
                 } else {
-                  _output.addError(
+                  output.addError(
                     InvalidEncodingException(headers!.rawHeaders),
                   );
                 }
@@ -61,19 +61,19 @@
                 isParsingHeaders = true;
               }
             } on DebugAdapterException catch (e) {
-              _output.addError(e);
-              _output.close();
+              output.addError(e);
+              output.close();
             }
           },
-          onError: _output.addError,
-          onDone: _output.close,
+          onError: output.addError,
+          onDone: output.close,
         );
       },
       onPause: () => input.pause(),
       onResume: () => input.resume(),
       onCancel: () => input.cancel(),
     );
-    return _output.stream;
+    return output.stream;
   }
 
   /// Whether [buffer] ends in '\r\n\r\n'.
diff --git a/dds/lib/src/dap/server.dart b/dds/lib/src/dap/server.dart
index 9b208a6..ddf0843 100644
--- a/dds/lib/src/dap/server.dart
+++ b/dds/lib/src/dap/server.dart
@@ -26,15 +26,15 @@
   final Logger? logger;
 
   DapServer(
-    Stream<List<int>> _input,
-    StreamSink<List<int>> _output, {
+    Stream<List<int>> input,
+    StreamSink<List<int>> output, {
     this.ipv6 = false,
     this.enableDds = true,
     this.enableAuthCodes = true,
     this.test = false,
     this.logger,
     Function? onError,
-  }) : channel = ByteStreamServerChannel(_input, _output, logger) {
+  }) : channel = ByteStreamServerChannel(input, output, logger) {
     adapter = test
         ? DartTestDebugAdapter(
             channel,
diff --git a/dds/lib/src/dap/stream_transformers.dart b/dds/lib/src/dap/stream_transformers.dart
index 7e4540a..225cd92 100644
--- a/dds/lib/src/dap/stream_transformers.dart
+++ b/dds/lib/src/dap/stream_transformers.dart
@@ -14,27 +14,27 @@
   @override
   Stream<String> bind(Stream<List<int>> stream) {
     late StreamSubscription<int> input;
-    late StreamController<String> _output;
+    late StreamController<String> output;
     final buffer = <int>[];
-    _output = StreamController<String>(
+    output = StreamController<String>(
       onListen: () {
         input = stream.expand((b) => b).listen(
           (codeUnit) {
             buffer.add(codeUnit);
             if (_endsWithLf(buffer)) {
-              _output.add(utf8.decode(buffer));
+              output.add(utf8.decode(buffer));
               buffer.clear();
             }
           },
-          onError: _output.addError,
-          onDone: _output.close,
+          onError: output.addError,
+          onDone: output.close,
         );
       },
       onPause: () => input.pause(),
       onResume: () => input.resume(),
       onCancel: () => input.cancel(),
     );
-    return _output.stream;
+    return output.stream;
   }
 
   /// Whether [buffer] ends in '\n'.
diff --git a/dds/lib/src/dap/variables.dart b/dds/lib/src/dap/variables.dart
index cd9ebea..d6b0387 100644
--- a/dds/lib/src/dap/variables.dart
+++ b/dds/lib/src/dap/variables.dart
@@ -2,6 +2,8 @@
 // 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:vm_service/vm_service.dart';
+
 import 'protocol_generated.dart';
 
 /// A wrapper around variables for use in `variablesRequest` that can hold
@@ -14,6 +16,29 @@
   VariableData(this.data, this.format);
 }
 
+/// A wrapper around variables for use in `variablesRequest` that can hold
+/// additional data, such as a formatting information supplied in an evaluation
+/// request.
+class FrameScopeData {
+  final Frame frame;
+  final FrameScopeDataKind kind;
+
+  FrameScopeData(this.frame, this.kind);
+}
+
+enum FrameScopeDataKind {
+  locals,
+  globals,
+}
+
+/// A wrapper around a variable for use in `variablesRequest` that holds
+/// an instance sent for inspection.
+class InspectData {
+  final InstanceRef? instance;
+
+  InspectData(this.instance);
+}
+
 /// Formatting preferences for a variable.
 class VariableFormat {
   /// Whether to supress quotes around [String]s.
diff --git a/dds/lib/src/dds_impl.dart b/dds/lib/src/dds_impl.dart
index 3c2cebd..73c0fa4 100644
--- a/dds/lib/src/dds_impl.dart
+++ b/dds/lib/src/dds_impl.dart
@@ -422,6 +422,7 @@
   String? getNamespace(DartDevelopmentServiceClient client) =>
       clientManager.clients.keyOf(client);
 
+  @override
   bool get authCodesEnabled => _authCodesEnabled;
   final bool _authCodesEnabled;
   String? get authCode => _authCode;
@@ -430,11 +431,12 @@
   final bool _enableServicePortFallback;
   final bool shouldLogRequests;
 
+  @override
   Uri get remoteVmServiceUri => _remoteVmServiceUri;
 
   @override
   Uri get remoteVmServiceWsUri => _toWebSocket(_remoteVmServiceUri)!;
-  Uri _remoteVmServiceUri;
+  final Uri _remoteVmServiceUri;
 
   @override
   Uri? get uri => _uri;
@@ -453,6 +455,7 @@
     return _devToolsUri;
   }
 
+  @override
   void setExternalDevToolsUri(Uri uri) {
     if (_devToolsConfiguration?.enable ?? false) {
       throw StateError('A hosted DevTools instance is already being served.');
@@ -464,15 +467,18 @@
 
   final bool _ipv6;
 
+  @override
   bool get isRunning => _uri != null;
 
   final DevToolsConfiguration? _devToolsConfiguration;
 
+  @override
   List<String> get cachedUserTags => UnmodifiableListView(_cachedUserTags);
   final List<String> _cachedUserTags;
 
+  @override
   Future<void> get done => _done.future;
-  Completer _done = Completer<void>();
+  final Completer _done = Completer<void>();
   bool _initializationComplete = false;
   bool _shuttingDown = false;
 
diff --git a/dds/lib/src/devtools/client.dart b/dds/lib/src/devtools/client.dart
index c8543d2..0121a2c 100644
--- a/dds/lib/src/devtools/client.dart
+++ b/dds/lib/src/devtools/client.dart
@@ -2,6 +2,8 @@
 // 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.
 
+// ignore_for_file: implementation_imports
+
 import 'dart:async';
 
 import 'package:devtools_shared/devtools_server.dart';
diff --git a/dds/lib/src/devtools/handler.dart b/dds/lib/src/devtools/handler.dart
index f319d64..47377cd 100644
--- a/dds/lib/src/devtools/handler.dart
+++ b/dds/lib/src/devtools/handler.dart
@@ -51,7 +51,7 @@
   /// A wrapper around [devtoolsStaticAssetHandler] that handles serving
   /// index.html up for / and non-file requests like /memory, /inspector, etc.
   /// with the correct base href for the DevTools root.
-  final devtoolsAssetHandler = (Request request) {
+  FutureOr<Response> devtoolsAssetHandler(Request request) {
     // To avoid hard-coding a set of page names here (or needing access to one
     // from DevTools, assume any single-segment path with no extension is a
     // DevTools page that needs to serve up index.html).
@@ -68,7 +68,7 @@
     }
 
     return devtoolsStaticAssetHandler(request);
-  };
+  }
 
   // Support DevTools client-server interface via SSE.
   // Note: the handler path needs to match the full *original* path, not the
@@ -89,7 +89,7 @@
     ),
   );
 
-  final devtoolsHandler = (Request request) {
+  FutureOr<Response> devtoolsHandler(Request request) {
     // If the request isn't of the form api/<method> assume it's a request for
     // DevTools assets.
     if (request.url.pathSegments.length < 2 ||
@@ -109,7 +109,7 @@
       return Response.notFound('$method is not a valid API');
     }
     return ServerApi.handle(request);
-  };
+  }
 
   return (Request request) {
     if (notFoundHandler != null) {
diff --git a/dds/lib/src/devtools/machine_mode_command_handler.dart b/dds/lib/src/devtools/machine_mode_command_handler.dart
index e98f23e..e41871f 100644
--- a/dds/lib/src/devtools/machine_mode_command_handler.dart
+++ b/dds/lib/src/devtools/machine_mode_command_handler.dart
@@ -48,7 +48,7 @@
     required String devToolsUrl,
     required bool headlessMode,
   }) async {
-    final Stream<Map<String, dynamic>> _stdinCommandStream = stdin
+    final Stream<Map<String, dynamic>> stdinCommandStream = stdin
         .transform<String>(utf8.decoder)
         .transform<String>(const LineSplitter())
         .where((String line) => line.startsWith('{') && line.endsWith('}'))
@@ -64,7 +64,7 @@
     //     "uri":"<vm-service-uri-here>",
     //   }
     // }
-    _stdinCommandStream.listen((Map<String, dynamic> json) async {
+    stdinCommandStream.listen((Map<String, dynamic> json) async {
       // ID can be String, int or null
       final dynamic id = json['id'];
       final Map<String, dynamic> params = json['params'] ?? <String, dynamic>{};
diff --git a/dds/lib/src/devtools/memory_profile.dart b/dds/lib/src/devtools/memory_profile.dart
index df02db5..3221e76 100644
--- a/dds/lib/src/devtools/memory_profile.dart
+++ b/dds/lib/src/devtools/memory_profile.dart
@@ -339,13 +339,10 @@
       return '0' * (length - result.length) + result;
     }
 
-    return toStringLength(value.hour, 2) +
-        ':' +
-        toStringLength(value.minute, 2) +
-        ':' +
-        toStringLength(value.second, 2) +
-        '.' +
-        toStringLength(value.millisecond, 3);
+    return '${toStringLength(value.hour, 2)}:'
+        '${toStringLength(value.minute, 2)}:'
+        '${toStringLength(value.second, 2)}.'
+        '${toStringLength(value.millisecond, 3)}';
   }
 }
 
diff --git a/dds/lib/src/isolate_manager.dart b/dds/lib/src/isolate_manager.dart
index 837fd39..b9f9382 100644
--- a/dds/lib/src/isolate_manager.dart
+++ b/dds/lib/src/isolate_manager.dart
@@ -37,8 +37,8 @@
   pausePostRequest,
 }
 
-class _RunningIsolate {
-  _RunningIsolate(this.isolateManager, this.id, this.name)
+class RunningIsolate {
+  RunningIsolate(this.isolateManager, this.id, this.name)
       : cpuSamplesManager = CpuSamplesManager(
           isolateManager.dds,
           id,
@@ -233,7 +233,7 @@
           }
           final name = isolate['name'];
           if (isolate.containsKey('pauseEvent')) {
-            isolates[id] = _RunningIsolate(this, id, name);
+            isolates[id] = RunningIsolate(this, id, name);
             final eventKind = isolate['pauseEvent']['kind'];
             _updateIsolateState(id, name, eventKind);
           } else {
@@ -256,7 +256,7 @@
 
   /// Initializes state for a newly started isolate.
   void isolateStarted(String id, String name) {
-    final isolate = _RunningIsolate(this, id, name);
+    final isolate = RunningIsolate(this, id, name);
     isolate.running();
     isolates[id] = isolate;
   }
@@ -324,5 +324,5 @@
   bool _initialized = false;
   final DartDevelopmentServiceImpl dds;
   final _mutex = Mutex();
-  final Map<String, _RunningIsolate> isolates = {};
+  final Map<String, RunningIsolate> isolates = {};
 }
diff --git a/dds/lib/src/named_lookup.dart b/dds/lib/src/named_lookup.dart
index 7671d28..895893d 100644
--- a/dds/lib/src/named_lookup.dart
+++ b/dds/lib/src/named_lookup.dart
@@ -32,6 +32,7 @@
 
   String? keyOf(E e) => _ids[e];
 
+  @override
   Iterator<E> get iterator => _ids.keys.iterator;
 }
 
diff --git a/dds/lib/src/stream_manager.dart b/dds/lib/src/stream_manager.dart
index 15dcea3..28a0e5b 100644
--- a/dds/lib/src/stream_manager.dart
+++ b/dds/lib/src/stream_manager.dart
@@ -78,7 +78,7 @@
           'kind': 'ServiceRegistered',
           'timestamp': DateTime.now().millisecondsSinceEpoch,
           'service': service,
-          'method': namespace + '.' + service,
+          'method': '$namespace.$service',
           'alias': alias,
         }
       };
@@ -110,7 +110,7 @@
             'kind': 'ServiceUnregistered',
             'timestamp': DateTime.now().millisecondsSinceEpoch,
             'service': service,
-            'method': namespace! + '.' + service,
+            'method': '$namespace.$service',
           },
         },
         excludedClient: client,
diff --git a/dds/pubspec.yaml b/dds/pubspec.yaml
index 0c386bf..ff715db 100644
--- a/dds/pubspec.yaml
+++ b/dds/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dds
-version: 2.7.5
+version: 2.7.7
 description: >-
   A library used to spawn the Dart Developer Service, used to communicate with
   a Dart VM Service instance.
diff --git a/dds/tool/dap/codegen.dart b/dds/tool/dap/codegen.dart
index 630cf37..ea846da 100644
--- a/dds/tool/dap/codegen.dart
+++ b/dds/tool/dap/codegen.dart
@@ -480,7 +480,7 @@
     final nullOp = isOptional ? '?' : '';
 
     if (baseType.isAny || baseType.isSimple) {
-      buffer.write('$valueCode');
+      buffer.write(valueCode);
       if (dartType != 'Object?') {
         buffer.write(' as $dartType');
       }
diff --git a/dds/tool/dap/generate_all.dart b/dds/tool/dap/generate_all.dart
index ec769fe..5247e6e 100644
--- a/dds/tool/dap/generate_all.dart
+++ b/dds/tool/dap/generate_all.dart
@@ -44,7 +44,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// This code was auto-generated by tool/dap/generate_all.dart - do not hand-edit!
+// This code was auto-generated by tool/dap/generate_all.dart; do not hand-edit!
+
+// ignore_for_file: prefer_void_to_null
 
 import 'protocol_common.dart';
 import 'protocol_special.dart';
diff --git a/dds/tool/dap/json_schema_extensions.dart b/dds/tool/dap/json_schema_extensions.dart
index 2d8dd13..c73e0f5 100644
--- a/dds/tool/dap/json_schema_extensions.dart
+++ b/dds/tool/dap/json_schema_extensions.dart
@@ -103,7 +103,7 @@
   /// Whether this type is a simple type that needs no special handling for
   /// deserialization (such as `String`, `bool`, `int`, `Map<String, Object?>`).
   bool get isSimple {
-    const _dartSimpleTypes = {
+    const dartSimpleTypes = {
       'bool',
       'int',
       'num',
@@ -112,7 +112,7 @@
       'Null',
     };
     return type != null &&
-        _dartSimpleTypes.contains(type!.map(_toDartType, _toDartUnionType));
+        dartSimpleTypes.contains(type!.map(_toDartType, _toDartUnionType));
   }
 
   /// Whether this type is a Union type using JSON schema's "oneOf" of where its
diff --git a/dds/tool/devtools_server/serve_local.dart b/dds/tool/devtools_server/serve_local.dart
index 86ac23a..6819794 100644
--- a/dds/tool/devtools_server/serve_local.dart
+++ b/dds/tool/devtools_server/serve_local.dart
@@ -48,7 +48,7 @@
 
   // serve_local.dart --devtools-build=foo
   // serve_local.dart --devtools-build="foo"
-  args.removeWhere((arg) => arg.startsWith('${option}='));
+  args.removeWhere((arg) => arg.startsWith('$option='));
 
   return args;
 }
diff --git a/devtools_shared/BUILD.gn b/devtools_shared/BUILD.gn
index c200000..7aaeab3 100644
--- a/devtools_shared/BUILD.gn
+++ b/devtools_shared/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for devtools_shared-2.21.1
+# This file is generated by package_importer.py for devtools_shared-2.22.2
 
 import("//build/dart/dart_library.gni")
 
diff --git a/devtools_shared/pubspec.yaml b/devtools_shared/pubspec.yaml
index 49621db..0a21497 100644
--- a/devtools_shared/pubspec.yaml
+++ b/devtools_shared/pubspec.yaml
@@ -1,7 +1,7 @@
 name: devtools_shared
 description: Package of shared structures between devtools_app, dds, and other tools.
 
-version: 2.21.1
+version: 2.22.2
 
 repository: https://github.com/flutter/devtools/tree/master/packages/devtools_shared
 
diff --git a/dwds/BUILD.gn b/dwds/BUILD.gn
index 0d85f99..529353c 100644
--- a/dwds/BUILD.gn
+++ b/dwds/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for dwds-17.0.0
+# This file is generated by package_importer.py for dwds-18.0.2
 
 import("//build/dart/dart_library.gni")
 
@@ -66,6 +66,8 @@
     "data/serializers.g.dart",
     "dwds.dart",
     "expression_compiler.dart",
+    "sdk_configuration.dart",
+    "shared/batched_stream.dart",
     "src/connections/app_connection.dart",
     "src/connections/debug_connection.dart",
     "src/debugging/classes.dart",
@@ -110,16 +112,15 @@
     "src/services/expression_compiler_service.dart",
     "src/services/expression_evaluator.dart",
     "src/sockets.dart",
-    "src/utilities/batched_stream.dart",
     "src/utilities/conversions.dart",
     "src/utilities/dart_uri.dart",
     "src/utilities/ddc_names.dart",
     "src/utilities/domain.dart",
     "src/utilities/objects.dart",
     "src/utilities/sdk_configuration.dart",
+    "src/utilities/server.dart",
     "src/utilities/shared.dart",
     "src/utilities/synchronized.dart",
     "src/version.dart",
-    "src/web_utilities/batched_stream.dart",
   ]
 }
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index 677abae..adc1100 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -1,3 +1,38 @@
+## 18.0.2
+
+- Support new DDC temp names for patterns. - [#2042](https://github.com/dart-lang/webdev/pull/2042)
+- Make debugger find next dart location when stepping. -[#2043](https://github.com/dart-lang/webdev/pull/2043)
+
+## 18.0.1
+
+- Fix failure to map JS exceptions to dart. - [#2004](https://github.com/dart-lang/webdev/pull/2004)
+- Fix for listening to custom streams. - [#2011](https://github.com/dart-lang/webdev/pull/2011)
+- Handle unexpected extension debugger disconnect events without crashing the app - [#2021](https://github.com/dart-lang/webdev/pull/2021)
+- Support `Set` inspection. - [#2024](https://github.com/dart-lang/webdev/pull/2024)
+
+## 18.0.0
+
+- Cleanup `getObject` code for lists and maps.
+  - Now works with offset `0` and `null` count.
+  - Fix failures on edge cases.
+- Support records:
+  - Update SDK constraint to `>=3.0.0-188.0.dev <4.0.0`.
+  - Update `package:vm_service` constraint to `>=10.1.2 <12.0.0`.
+  - Update `package:dds` constraint to `^2.7.1`.
+  - Fill `BoundField.name` for records.
+  - Display records as a container of fields.
+- Remove test-only code from `sdk_configuration.dart`.
+- Move shared test-only code to a new `test_common` package.
+- Convert unnecessary async code to sync.
+- Allow empty scopes in expression evaluation in a frame.
+
+**Breaking changes**
+
+- Require `sdkConfigurationProvider` in `ExpressionCompilerService`
+  constructor.
+- Change DWDS parameter `isFlutterApp` from type `bool?` to type
+  `Future<bool> Function()?`.
+
 ## 17.0.0
 
 - Include debug information in the event sent from the injected client to the
@@ -22,10 +57,12 @@
 - Fix expression compiler throwing when weak SDK summary is not found.
 
 **Breaking changes**
+
 - Include an optional param to `Dwds.start` to indicate whether it is running
   internally or externally.
 - Include an optional param to `Dwds.start` to indicate whether it a Flutter
   app or not.
+- Remove `sdkConfigurationProvider` parameter from `Dwds.start`.
 - Remove deprecated `ChromeProxyService.setExceptionPauseMode()`.
 - Support dart 3.0-alpha breaking changes:
   - Generate missing SDK assets for tests.
diff --git a/dwds/analysis_options.yaml b/dwds/analysis_options.yaml
index 3106b5c..a778600 100644
--- a/dwds/analysis_options.yaml
+++ b/dwds/analysis_options.yaml
@@ -15,5 +15,9 @@
 
 linter:
   rules:
+    - always_use_package_imports
+    - directives_ordering
     - prefer_final_locals
     - unawaited_futures
+    - avoid_void_async
+    - unnecessary_lambdas
diff --git a/dwds/debug_extension/pubspec.yaml b/dwds/debug_extension/pubspec.yaml
index eec4a91..82584e6 100644
--- a/dwds/debug_extension/pubspec.yaml
+++ b/dwds/debug_extension/pubspec.yaml
@@ -6,7 +6,7 @@
   A chrome extension for Dart debugging.
 
 environment:
-  sdk: ">=3.0.0-134.0.dev <4.0.0"
+  sdk: ">=3.0.0-188.0.dev <4.0.0"
 
 dependencies:
   async: ^2.3.0
diff --git a/dwds/debug_extension/tool/copy_builder.dart b/dwds/debug_extension/tool/copy_builder.dart
index 8cd1142..c9e7169 100644
--- a/dwds/debug_extension/tool/copy_builder.dart
+++ b/dwds/debug_extension/tool/copy_builder.dart
@@ -21,7 +21,7 @@
       };
 
   @override
-  void build(BuildStep buildStep) async {
+  Future<void> build(BuildStep buildStep) async {
     final inputAsset = buildStep.inputId;
     final allowedOutputs = buildStep.allowedOutputs;
 
diff --git a/dwds/debug_extension/tool/update_dev_files.dart b/dwds/debug_extension/tool/update_dev_files.dart
index ece7413..a17d2fe 100644
--- a/dwds/debug_extension/tool/update_dev_files.dart
+++ b/dwds/debug_extension/tool/update_dev_files.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:io';
 
 void main() async {
diff --git a/dwds/debug_extension/web/background.dart b/dwds/debug_extension/web/background.dart
index 08cb079..210a498 100644
--- a/dwds/debug_extension/web/background.dart
+++ b/dwds/debug_extension/web/background.dart
@@ -19,12 +19,8 @@
 import 'package:dwds/data/devtools_request.dart';
 import 'package:dwds/data/extension_request.dart';
 import 'package:dwds/data/serializers.dart';
+import 'package:dwds/shared/batched_stream.dart';
 import 'package:dwds/src/sockets.dart';
-// NOTE(annagrin): using 'package:dwds/src/utilities/batched_stream.dart'
-// makes dart2js skip creating background.js, so we use a copy instead.
-// import 'package:dwds/src/utilities/batched_stream.dart';
-// Issue: https://github.com/dart-lang/sdk/issues/49973
-import 'package:dwds/src/web_utilities/batched_stream.dart';
 import 'package:js/js.dart';
 import 'package:js/js_util.dart' as js_util;
 import 'package:pub_semver/pub_semver.dart';
@@ -237,7 +233,7 @@
   }));
 }
 
-void _attachDebuggerToTab(Tab currentTab) async {
+Future<void> _attachDebuggerToTab(Tab currentTab) async {
   if (!_debuggableTabs.contains(currentTab.id)) return;
 
   if (_tabIdToWarning.containsKey(currentTab.id)) {
@@ -291,7 +287,7 @@
   }
 }
 
-void _maybeMarkTabAsDebuggable(
+Future<void> _maybeMarkTabAsDebuggable(
     Request request, MessageSender sender, Function sendResponse) async {
   // Register any warnings for the tab:
   if (request.warning != '') {
@@ -304,7 +300,7 @@
   sendResponse(true);
 }
 
-void _maybeAttachDebugSession(
+Future<void> _maybeAttachDebugSession(
   Debuggee source,
   String method,
   Object? params,
@@ -365,7 +361,7 @@
   }
 }
 
-void _maybeSaveDevToolsTabId(Tab tab) async {
+Future<void> _maybeSaveDevToolsTabId(Tab tab) async {
   // Remembers the ID of the DevTools tab.
   //
   // This assumes that the next launched tab after a session is created is the
@@ -373,7 +369,7 @@
   if (_debugSessions.isNotEmpty) _debugSessions.last.devtoolsTabId ??= tab.id;
 }
 
-void _handleMessageFromExternalExtensions(
+Future<void> _handleMessageFromExternalExtensions(
     dynamic jsRequest, MessageSender sender, Function sendResponse) async {
   if (jsRequest == null) return;
   final request = jsRequest as Request;
@@ -412,7 +408,7 @@
   }
 }
 
-void _forwardMessageToExternalExtensions(
+Future<void> _forwardMessageToExternalExtensions(
     Debuggee source, String method, Object? params) async {
   if (_allowedEvents.contains(method)) {
     sendMessageToExtensions(ExternalExtensionRequest(
diff --git a/dwds/debug_extension_mv3/pubspec.yaml b/dwds/debug_extension_mv3/pubspec.yaml
index e043196..7130c4a 100644
--- a/dwds/debug_extension_mv3/pubspec.yaml
+++ b/dwds/debug_extension_mv3/pubspec.yaml
@@ -1,12 +1,12 @@
 name: mv3_extension
 publish_to: none
-version: 1.30.0
+version: 1.33.0
 homepage: https://github.com/dart-lang/webdev
 description: >-
   A Chrome extension for Dart debugging.
 
 environment:
-  sdk: ">=3.0.0-134.0.dev <4.0.0"
+  sdk: ">=3.0.0-188.0.dev <4.0.0"
 
 dependencies:
   built_value: ^8.3.0
diff --git a/dwds/debug_extension_mv3/tool/build_extension.dart b/dwds/debug_extension_mv3/tool/build_extension.dart
new file mode 100644
index 0000000..20292bf
--- /dev/null
+++ b/dwds/debug_extension_mv3/tool/build_extension.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2023, 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.
+
+// INSTRUCTIONS:
+
+// Builds the unminifed dart2js extension (see DDC issue:
+// see DDC issue: https://github.com/dart-lang/sdk/issues/49869).
+
+// Run from the extension root directory:
+//    - For dev: dart run tool/build_extension.dart
+//    - For prod: dart run tool/build_extension.dart prod
+//    - For MV3: dart run tool/build_extension.dart --mv3
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:path/path.dart' as p;
+
+const _prodFlag = 'prod';
+const _mv3Flag = 'mv3';
+
+void main(List<String> arguments) async {
+  final parser = ArgParser()
+    ..addFlag(_prodFlag, negatable: true, defaultsTo: false)
+    ..addFlag(_mv3Flag, negatable: true, defaultsTo: false);
+  final argResults = parser.parse(arguments);
+
+  exitCode = await run(
+    isProd: argResults[_prodFlag] as bool,
+    isMV3: argResults[_mv3Flag] as bool,
+  );
+  if (exitCode != 0) {
+    _logWarning('Run terminated unexpectedly with exit code: $exitCode');
+  }
+}
+
+Future<int> run({required bool isProd, required bool isMV3}) async {
+  _logInfo(
+      'Building ${isMV3 ? 'MV3' : 'MV2'} extension for ${isProd ? 'prod' : 'dev'}');
+  _logInfo('Compiling extension with dart2js to /compiled directory');
+  final compileStep = await Process.start(
+    'dart',
+    ['run', 'build_runner', 'build', 'web', '--output', 'build', '--release'],
+  );
+  final compileExitCode = await _handleProcess(compileStep);
+  // Terminate early if compilation failed:
+  if (compileExitCode != 0) {
+    return compileExitCode;
+  }
+  final manifestFileName = isMV3 ? 'manifest_mv3' : 'manifest_mv2';
+  _logInfo('Copying manifest.json to /compiled directory');
+  try {
+    File(p.join('web', '$manifestFileName.json')).copySync(
+      p.join('compiled', 'manifest.json'),
+    );
+  } catch (error) {
+    _logWarning('Copying manifest file failed: $error');
+    // Return non-zero exit code to indicate failure:
+    return 1;
+  }
+  // If we're compiling for prod, skip updating the manifest.json:
+  if (isProd) return 0;
+  // Update manifest.json for dev:
+  _logInfo('Updating manifest.json in /compiled directory.');
+  final updateStep = await Process.start(
+    'dart',
+    [p.join('tool', 'update_dev_files.dart')],
+  );
+  final updateExitCode = await _handleProcess(updateStep);
+  // Return exit code (0 indicates success):
+  return updateExitCode;
+}
+
+Future<int> _handleProcess(Process process) async {
+  _handleOutput(process.stdout, isStdout: true);
+  _handleOutput(process.stderr, isStdout: false);
+  return process.exitCode;
+}
+
+void _handleOutput(Stream<List<int>> output, {bool isStdout = true}) {
+  output
+      .transform(utf8.decoder)
+      .transform(const LineSplitter())
+      .listen((line) => _handleOutputLine(line, isStdout: isStdout));
+}
+
+void _handleOutputLine(String line, {bool isStdout = true}) {
+  // Skip empty lines:
+  if (line.isEmpty) return;
+  // Log any unexpected errors and throw:
+  final outputName = isStdout ? 'stdout' : 'stderr';
+  if (line.toUpperCase().contains('SEVERE') ||
+      line.toUpperCase().contains('ERROR')) {
+    final error = 'Unexpected error in $outputName: $line';
+    _logWarning(error);
+    throw Exception(error);
+  }
+  // Log message to the terminal:
+  final message = '$outputName: $line';
+  isStdout ? _logInfo(message) : _logWarning(message);
+}
+
+void _logInfo(String message) {
+  stdout.writeln(message);
+}
+
+void _logWarning(String warning) {
+  stderr.writeln(warning);
+}
diff --git a/dwds/debug_extension_mv3/tool/build_extension.sh b/dwds/debug_extension_mv3/tool/build_extension.sh
deleted file mode 100755
index 9df8f4f..0000000
--- a/dwds/debug_extension_mv3/tool/build_extension.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-# Copyright 2022 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# INSTRUCTIONS:
-
-# Builds the unminifed dart2js app (see DDC issue: https://github.com/dart-lang/sdk/issues/49869):
-# ./tool/build_extension.sh
-
-
-prod="false"
-
-case "$1" in
-    prod)
-        prod="true"
-        shift;;
-esac
-
-echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-echo "Building dart2js-compiled extension to /compiled directory."
-echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-dart run build_runner build web --output build --release
-
-if [ $prod == true ]; then
-    exit 1
-fi
-
-echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-echo "Updating manifest.json in /compiled directory."
-echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
-dart tool/update_dev_files.dart
diff --git a/dwds/debug_extension_mv3/tool/copy_builder.dart b/dwds/debug_extension_mv3/tool/copy_builder.dart
index 93875c9..cf0db0a 100644
--- a/dwds/debug_extension_mv3/tool/copy_builder.dart
+++ b/dwds/debug_extension_mv3/tool/copy_builder.dart
@@ -18,7 +18,7 @@
       };
 
   @override
-  void build(BuildStep buildStep) async {
+  Future<void> build(BuildStep buildStep) async {
     final inputAsset = buildStep.inputId;
     final allowedOutputs = buildStep.allowedOutputs;
 
diff --git a/dwds/debug_extension_mv3/tool/update_dev_files.dart b/dwds/debug_extension_mv3/tool/update_dev_files.dart
index 7cc2f48..ea8ed01 100644
--- a/dwds/debug_extension_mv3/tool/update_dev_files.dart
+++ b/dwds/debug_extension_mv3/tool/update_dev_files.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:io';
 
 void main() async {
@@ -22,7 +21,7 @@
         _newKeyValue(
           oldLine: line,
           newKey: 'name',
-          newValue: '[DEV] MV3 Dart Debug Extension',
+          newValue: '[DEV] Dart Debug Extension',
         ),
         if (extensionKey != null)
           _newKeyValue(
@@ -31,6 +30,14 @@
             newValue: extensionKey,
           ),
       ];
+    } else if (_matchesKey(line: line, key: 'default_icon')) {
+      return [
+        _newKeyValue(
+          oldLine: line,
+          newKey: 'default_icon',
+          newValue: 'static_assets/dart_dev.png',
+        )
+      ];
     } else {
       return [line];
     }
diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart
index 10436c6..878b79e 100644
--- a/dwds/debug_extension_mv3/web/background.dart
+++ b/dwds/debug_extension_mv3/web/background.dart
@@ -5,25 +5,17 @@
 @JS()
 library background;
 
-import 'dart:async';
-import 'dart:html';
-
 import 'package:dwds/data/debug_info.dart';
-import 'package:dwds/data/extension_request.dart';
 import 'package:js/js.dart';
 
-import 'data_types.dart';
-import 'debug_session.dart';
 import 'chrome_api.dart';
 import 'cross_extension_communication.dart';
-import 'lifeline_ports.dart';
+import 'data_types.dart';
+import 'debug_session.dart';
 import 'logger.dart';
 import 'messaging.dart';
 import 'storage.dart';
 import 'utils.dart';
-import 'web_api.dart';
-
-const _authSuccessResponse = 'Dart Debug Authentication Success!';
 
 void main() {
   _registerListeners();
@@ -39,83 +31,51 @@
   chrome.runtime.onMessageExternal.addListener(
     allowInterop(handleMessagesFromAngularDartDevTools),
   );
-  chrome.tabs.onRemoved
-      .addListener(allowInterop((tabId, _) => maybeRemoveLifelinePort(tabId)));
   // Update the extension icon on tab navigation:
-  chrome.tabs.onActivated.addListener(allowInterop((ActiveInfo info) {
-    _updateIcon(info.tabId);
+  chrome.tabs.onActivated.addListener(allowInterop((ActiveInfo info) async {
+    await _updateIcon(info.tabId);
   }));
   chrome.windows.onFocusChanged.addListener(allowInterop((_) async {
-    final currentTab = await _getTab();
+    final currentTab = await activeTab;
     if (currentTab?.id != null) {
-      _updateIcon(currentTab!.id);
+      await _updateIcon(currentTab!.id);
     }
   }));
   chrome.webNavigation.onCommitted
       .addListener(allowInterop(_detectNavigationAwayFromDartApp));
 
   // Detect clicks on the Dart Debug Extension icon.
-  chrome.action.onClicked.addListener(allowInterop(
-    (Tab tab) => _startDebugSession(
+  onExtensionIconClicked(allowInterop(
+    (Tab tab) => attachDebugger(
       tab.id,
       trigger: Trigger.extensionIcon,
     ),
   ));
 }
 
-Future<void> _startDebugSession(int tabId, {required Trigger trigger}) async {
-  final debugInfo = await _fetchDebugInfo(tabId);
-  final extensionUrl = debugInfo?.extensionUrl;
-  if (extensionUrl == null) {
-    _showWarningNotification('Can\'t debug Dart app. Extension URL not found.');
-    sendConnectFailureMessage(
-      ConnectFailureReason.noDartApp,
-      dartAppTabId: tabId,
-    );
-    return;
-  }
-  final isAuthenticated = await _authenticateUser(extensionUrl, tabId);
-  if (!isAuthenticated) {
-    sendConnectFailureMessage(
-      ConnectFailureReason.authentication,
-      dartAppTabId: tabId,
-    );
-    return;
-  }
-
-  maybeCreateLifelinePort(tabId);
-  attachDebugger(tabId, trigger: trigger);
-}
-
-Future<bool> _authenticateUser(String extensionUrl, int tabId) async {
-  final authUrl = _constructAuthUrl(extensionUrl).toString();
-  final response = await fetchRequest(authUrl);
-  final responseBody = response.body ?? '';
-  if (!responseBody.contains(_authSuccessResponse)) {
-    debugError('Not authenticated: ${response.status} / $responseBody',
-        verbose: true);
-    _showWarningNotification('Please re-authenticate and try again.');
-    await createTab(authUrl, inNewWindow: false);
-    return false;
-  }
-  return true;
-}
-
-Uri _constructAuthUrl(String extensionUrl) {
-  final authUri = Uri.parse(extensionUrl).replace(path: authenticationPath);
-  if (authUri.scheme == 'ws') {
-    return authUri.replace(scheme: 'http');
-  }
-  if (authUri.scheme == 'wss') {
-    return authUri.replace(scheme: 'https');
-  }
-  return authUri;
-}
-
-void _handleRuntimeMessages(
+Future<void> _handleRuntimeMessages(
     dynamic jsRequest, MessageSender sender, Function sendResponse) async {
   if (jsRequest is! String) return;
 
+  interceptMessage<String>(
+      message: jsRequest,
+      expectedType: MessageType.isAuthenticated,
+      expectedSender: Script.detector,
+      expectedRecipient: Script.background,
+      messageHandler: (String isAuthenticated) async {
+        final dartTab = sender.tab;
+        if (dartTab == null) {
+          debugWarn('Received auth info but tab is missing.');
+          return;
+        }
+        // Save the authentication info in storage:
+        await setStorageObject<String>(
+          type: StorageObject.isAuthenticated,
+          value: isAuthenticated,
+          tabId: dartTab.id,
+        );
+      });
+
   interceptMessage<DebugInfo>(
       message: jsRequest,
       expectedType: MessageType.debugInfo,
@@ -127,13 +87,19 @@
           debugWarn('Received debug info but tab is missing.');
           return;
         }
+        // If this is a new Dart app, we need to clear old debug session data:
+        if (!await _matchesAppInStorage(debugInfo.appId, tabId: dartTab.id)) {
+          await clearStaleDebugSession(dartTab.id);
+        }
         // Save the debug info for the Dart app in storage:
         await setStorageObject<DebugInfo>(
-            type: StorageObject.debugInfo, value: debugInfo, tabId: dartTab.id);
+            type: StorageObject.debugInfo,
+            value: _addTabUrl(debugInfo, tabUrl: dartTab.url),
+            tabId: dartTab.id);
         // Update the icon to show that a Dart app has been detected:
-        final currentTab = await _getTab();
+        final currentTab = await activeTab;
         if (currentTab?.id == dartTab.id) {
-          _setDebuggableIcon();
+          await _updateIcon(dartTab.id);
         }
       });
 
@@ -146,19 +112,44 @@
         final newState = debugStateChange.newState;
         final tabId = debugStateChange.tabId;
         if (newState == DebugStateChange.startDebugging) {
-          _startDebugSession(tabId, trigger: Trigger.extensionPanel);
+          attachDebugger(tabId, trigger: Trigger.extensionPanel);
         }
       });
+
+  interceptMessage<String>(
+      message: jsRequest,
+      expectedType: MessageType.multipleAppsDetected,
+      expectedSender: Script.detector,
+      expectedRecipient: Script.background,
+      messageHandler: (String multipleAppsDetected) async {
+        final dartTab = sender.tab;
+        if (dartTab == null) {
+          debugWarn('Received multiple apps detected but tab is missing.');
+          return;
+        }
+        // Save the multiple apps info in storage:
+        await setStorageObject<String>(
+          type: StorageObject.multipleAppsDetected,
+          value: multipleAppsDetected,
+          tabId: dartTab.id,
+        );
+        _setWarningIcon();
+      });
 }
 
-void _detectNavigationAwayFromDartApp(NavigationInfo navigationInfo) async {
+Future<void> _detectNavigationAwayFromDartApp(
+    NavigationInfo navigationInfo) async {
+  // Ignore any navigation events within the page itself (e.g., opening a link,
+  // reloading the page, reloading an IFRAME, etc):
+  if (_isInternalNavigation(navigationInfo)) return;
   final tabId = navigationInfo.tabId;
   final debugInfo = await _fetchDebugInfo(navigationInfo.tabId);
   if (debugInfo == null) return;
-  if (debugInfo.appUrl != navigationInfo.url) {
+  if (debugInfo.tabUrl != navigationInfo.url) {
     _setDefaultIcon();
+    await clearStaleDebugSession(tabId);
     await removeStorageObject(type: StorageObject.debugInfo, tabId: tabId);
-    detachDebugger(
+    await detachDebugger(
       tabId,
       type: TabType.dartApp,
       reason: DetachReason.navigatedAwayFromApp,
@@ -166,25 +157,55 @@
   }
 }
 
-void _updateIcon(int activeTabId) async {
+bool _isInternalNavigation(NavigationInfo navigationInfo) {
+  return [
+    'auto_subframe',
+    'form_submit',
+    'link',
+    'manual_subframe',
+    'reload',
+  ].contains(navigationInfo.transitionType);
+}
+
+DebugInfo _addTabUrl(DebugInfo debugInfo, {required String tabUrl}) {
+  return DebugInfo((b) => b
+    ..appEntrypointPath = debugInfo.appEntrypointPath
+    ..appId = debugInfo.appId
+    ..appInstanceId = debugInfo.appInstanceId
+    ..appOrigin = debugInfo.appOrigin
+    ..appUrl = debugInfo.appUrl
+    ..authUrl = debugInfo.authUrl
+    ..extensionUrl = debugInfo.extensionUrl
+    ..isInternalBuild = debugInfo.isInternalBuild
+    ..isFlutterApp = debugInfo.isFlutterApp
+    ..tabUrl = tabUrl);
+}
+
+Future<void> _updateIcon(int activeTabId) async {
   final debugInfo = await _fetchDebugInfo(activeTabId);
-  if (debugInfo != null) {
-    _setDebuggableIcon();
-  } else {
+  if (debugInfo == null) {
     _setDefaultIcon();
+    return;
   }
+  final multipleApps = await fetchStorageObject<String>(
+    type: StorageObject.multipleAppsDetected,
+    tabId: activeTabId,
+  );
+  multipleApps == null ? _setDebuggableIcon() : _setWarningIcon();
 }
 
 void _setDebuggableIcon() {
-  chrome.action
-      .setIcon(IconInfo(path: 'static_assets/dart.png'), /*callback*/ null);
+  setExtensionIcon(IconInfo(path: 'static_assets/dart.png'));
+}
+
+void _setWarningIcon() {
+  setExtensionIcon(IconInfo(path: 'static_assets/dart_warning.png'));
 }
 
 void _setDefaultIcon() {
-  final iconPath = isDevMode()
-      ? 'static_assets/dart_dev.png'
-      : 'static_assets/dart_grey.png';
-  chrome.action.setIcon(IconInfo(path: iconPath), /*callback*/ null);
+  final iconPath =
+      isDevMode ? 'static_assets/dart_dev.png' : 'static_assets/dart_grey.png';
+  setExtensionIcon(IconInfo(path: iconPath));
 }
 
 Future<DebugInfo?> _fetchDebugInfo(int tabId) {
@@ -194,21 +215,7 @@
   );
 }
 
-void _showWarningNotification(String message) {
-  chrome.notifications.create(
-    /*notificationId*/ null,
-    NotificationOptions(
-      title: '[Error] Dart Debug Extension',
-      message: message,
-      iconUrl: 'static_assets/dart.png',
-      type: 'basic',
-    ),
-    /*callback*/ null,
-  );
-}
-
-Future<Tab?> _getTab() async {
-  final query = QueryInfo(active: true, currentWindow: true);
-  final tabs = List<Tab>.from(await promiseToFuture(chrome.tabs.query(query)));
-  return tabs.isNotEmpty ? tabs.first : null;
+Future<bool> _matchesAppInStorage(String? appId, {required int tabId}) async {
+  final debugInfo = await _fetchDebugInfo(tabId);
+  return appId != null && appId == debugInfo?.appId;
 }
diff --git a/dwds/debug_extension_mv3/web/chrome_api.dart b/dwds/debug_extension_mv3/web/chrome_api.dart
index 77fc960..52c3983 100644
--- a/dwds/debug_extension_mv3/web/chrome_api.dart
+++ b/dwds/debug_extension_mv3/web/chrome_api.dart
@@ -12,42 +12,16 @@
 @JS()
 @anonymous
 class Chrome {
-  external Action get action;
   external Debugger get debugger;
   external Devtools get devtools;
   external Notifications get notifications;
   external Runtime get runtime;
-  external Scripting get scripting;
   external Storage get storage;
   external Tabs get tabs;
   external WebNavigation get webNavigation;
   external Windows get windows;
 }
 
-/// chrome.action APIs
-/// https://developer.chrome.com/docs/extensions/reference/action
-
-@JS()
-@anonymous
-class Action {
-  external void setIcon(IconInfo iconInfo, Function? callback);
-
-  external OnClickedHandler get onClicked;
-}
-
-@JS()
-@anonymous
-class OnClickedHandler {
-  external void addListener(void Function(Tab tab) callback);
-}
-
-@JS()
-@anonymous
-class IconInfo {
-  external String get path;
-  external factory IconInfo({String path});
-}
-
 /// chrome.debugger APIs:
 /// https://developer.chrome.com/docs/extensions/reference/debugger
 
@@ -57,7 +31,7 @@
   external void attach(
       Debuggee target, String requiredVersion, Function? callback);
 
-  external Object detach(Debuggee target);
+  external void detach(Debuggee target, Function? callback);
 
   external void sendCommand(Debuggee target, String method,
       Object? commandParams, Function? callback);
@@ -224,30 +198,6 @@
   external factory MessageSender({String? id, String? url, Tab? tab});
 }
 
-/// chrome.scripting APIs
-/// https://developer.chrome.com/docs/extensions/reference/scripting
-
-@JS()
-@anonymous
-class Scripting {
-  external executeScript(InjectDetails details, Function? callback);
-}
-
-@JS()
-@anonymous
-class InjectDetails<T, U> {
-  external Target get target;
-  external T? get func;
-  external List<U?>? get args;
-  external List<String>? get files;
-  external factory InjectDetails({
-    Target target,
-    T? func,
-    List<U>? args,
-    List<String>? files,
-  });
-}
-
 @JS()
 @anonymous
 class Target {
@@ -292,13 +242,14 @@
 @JS()
 @anonymous
 class Tabs {
-  external Object query(QueryInfo queryInfo);
+  external dynamic query(
+      QueryInfo queryInfo, void Function(List<Tab>) callback);
 
-  external Object create(TabInfo tabInfo);
+  external dynamic create(TabInfo tabInfo, void Function(Tab) callback);
 
-  external Object get(int tabId);
+  external dynamic get(int tabId, void Function(Tab?) callback);
 
-  external Object remove(int tabId);
+  external dynamic remove(int tabId, void Function()? callback);
 
   external OnActivatedHandler get onActivated;
 
@@ -378,7 +329,7 @@
 @JS()
 @anonymous
 class Windows {
-  external Object create(WindowInfo? createData);
+  external dynamic create(WindowInfo? createData, Function(WindowObj) callback);
 
   external OnFocusChangedHandler get onFocusChanged;
 }
diff --git a/dwds/debug_extension_mv3/web/cross_extension_communication.dart b/dwds/debug_extension_mv3/web/cross_extension_communication.dart
index 77faf5e..19a04f8 100644
--- a/dwds/debug_extension_mv3/web/cross_extension_communication.dart
+++ b/dwds/debug_extension_mv3/web/cross_extension_communication.dart
@@ -27,16 +27,16 @@
   'dwds.encodedUri',
 };
 
-void handleMessagesFromAngularDartDevTools(
+Future<void> handleMessagesFromAngularDartDevTools(
     dynamic jsRequest, MessageSender sender, Function sendResponse) async {
   if (jsRequest == null) return;
   final message = jsRequest as ExternalExtensionMessage;
   if (message.name == 'chrome.debugger.sendCommand') {
     _forwardCommandToChromeDebugger(message, sendResponse);
   } else if (message.name == 'dwds.encodedUri') {
-    _respondWithEncodedUri(message.tabId, sendResponse);
+    await _respondWithEncodedUri(message.tabId, sendResponse);
   } else if (message.name == 'dwds.startDebugging') {
-    attachDebugger(message.tabId, trigger: Trigger.angularDartDevTools);
+    await attachDebugger(message.tabId, trigger: Trigger.angularDartDevTools);
     sendResponse(true);
   } else {
     sendResponse(
@@ -83,7 +83,7 @@
   }
 }
 
-void _respondWithEncodedUri(int tabId, Function sendResponse) async {
+Future<void> _respondWithEncodedUri(int tabId, Function sendResponse) async {
   final encodedUri = await fetchStorageObject<String>(
       type: StorageObject.encodedUri, tabId: tabId);
   sendResponse(encodedUri ?? '');
diff --git a/dwds/debug_extension_mv3/web/data_serializers.g.dart b/dwds/debug_extension_mv3/web/data_serializers.g.dart
index 7c15ed1..5040b84 100644
--- a/dwds/debug_extension_mv3/web/data_serializers.g.dart
+++ b/dwds/debug_extension_mv3/web/data_serializers.g.dart
@@ -22,4 +22,4 @@
           () => new ListBuilder<ExtensionEvent>()))
     .build();
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/dwds/debug_extension_mv3/web/data_types.g.dart b/dwds/debug_extension_mv3/web/data_types.g.dart
index 34c78b4..dbc60df 100644
--- a/dwds/debug_extension_mv3/web/data_types.g.dart
+++ b/dwds/debug_extension_mv3/web/data_types.g.dart
@@ -243,7 +243,11 @@
 
   @override
   int get hashCode {
-    return $jf($jc($jc(0, tabId.hashCode), reason.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, tabId.hashCode);
+    _$hash = $jc(_$hash, reason.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -332,7 +336,10 @@
 
   @override
   int get hashCode {
-    return $jf($jc(0, newWindow.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, newWindow.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -415,7 +422,11 @@
 
   @override
   int get hashCode {
-    return $jf($jc($jc(0, tabId.hashCode), url.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, tabId.hashCode);
+    _$hash = $jc(_$hash, url.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -515,8 +526,12 @@
 
   @override
   int get hashCode {
-    return $jf(
-        $jc($jc($jc(0, tabId.hashCode), newState.hashCode), reason.hashCode));
+    var _$hash = 0;
+    _$hash = $jc(_$hash, tabId.hashCode);
+    _$hash = $jc(_$hash, newState.hashCode);
+    _$hash = $jc(_$hash, reason.hashCode);
+    _$hash = $jf(_$hash);
+    return _$hash;
   }
 
   @override
@@ -585,4 +600,4 @@
   }
 }
 
-// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/dwds/debug_extension_mv3/web/debug_session.dart b/dwds/debug_extension_mv3/web/debug_session.dart
index 8c19869..73fa864 100644
--- a/dwds/debug_extension_mv3/web/debug_session.dart
+++ b/dwds/debug_extension_mv3/web/debug_session.dart
@@ -7,17 +7,14 @@
 
 import 'dart:async';
 import 'dart:convert';
-import 'dart:html';
 
 import 'package:built_collection/built_collection.dart';
 import 'package:collection/collection.dart' show IterableExtension;
 import 'package:dwds/data/debug_info.dart';
 import 'package:dwds/data/devtools_request.dart';
 import 'package:dwds/data/extension_request.dart';
+import 'package:dwds/shared/batched_stream.dart';
 import 'package:dwds/src/sockets.dart';
-// TODO(https://github.com/dart-lang/sdk/issues/49973): Use conditional imports
-// in .../utilities/batched_stream so that we don't need to import a copy.
-import 'package:dwds/src/web_utilities/batched_stream.dart';
 import 'package:js/js.dart';
 import 'package:js/js_util.dart' as js_util;
 import 'package:sse/client/sse_client.dart';
@@ -27,6 +24,7 @@
 import 'cross_extension_communication.dart';
 import 'data_serializers.dart';
 import 'data_types.dart';
+import 'lifeline_ports.dart';
 import 'logger.dart';
 import 'messaging.dart';
 import 'storage.dart';
@@ -51,6 +49,7 @@
   connectionDoneEvent,
   devToolsTabClosed,
   navigatedAwayFromApp,
+  staleDebugSession,
   unknown;
 
   factory DetachReason.fromString(String value) {
@@ -80,7 +79,37 @@
   extensionIcon,
 }
 
-void attachDebugger(int dartAppTabId, {required Trigger trigger}) {
+enum DebuggerLocation {
+  angularDartDevTools,
+  chromeDevTools,
+  dartDevTools,
+  ide;
+
+  String get displayName {
+    switch (this) {
+      case DebuggerLocation.angularDartDevTools:
+        return 'AngularDart DevTools';
+      case DebuggerLocation.chromeDevTools:
+        return 'Chrome DevTools';
+      case DebuggerLocation.dartDevTools:
+        return 'a Dart DevTools tab';
+      case DebuggerLocation.ide:
+        return 'an IDE';
+    }
+  }
+}
+
+bool get existsActiveDebugSession => _debugSessions.isNotEmpty;
+
+int? get latestAppBeingDebugged =>
+    existsActiveDebugSession ? _debugSessions.last.appTabId : null;
+
+Future<void> attachDebugger(int dartAppTabId,
+    {required Trigger trigger}) async {
+  // Validate that the tab can be debugged:
+  final tabIsDebuggable = await _validateTabIsDebuggable(dartAppTabId);
+  if (!tabIsDebuggable) return;
+
   _tabIdToTrigger[dartAppTabId] = trigger;
   _registerDebugEventListeners();
   chrome.debugger.attach(
@@ -92,40 +121,94 @@
   );
 }
 
-void detachDebugger(
+Future<bool> detachDebugger(
   int tabId, {
   required TabType type,
   required DetachReason reason,
 }) async {
   final debugSession = _debugSessionForTab(tabId, type: type);
-  if (debugSession == null) return;
+  if (debugSession == null) return false;
   final debuggee = Debuggee(tabId: debugSession.appTabId);
-  final detachPromise = chrome.debugger.detach(debuggee);
-  await promiseToFuture(detachPromise);
-  final error = chrome.runtime.lastError;
-  if (error != null) {
-    debugWarn(
-        'Error detaching tab for reason: $reason. Error: ${error.message}');
+  final completer = Completer<bool>();
+  chrome.debugger.detach(debuggee, allowInterop(() {
+    final error = chrome.runtime.lastError;
+    if (error != null) {
+      debugWarn(
+          'Error detaching tab for reason: $reason. Error: ${error.message}');
+      completer.complete(false);
+    } else {
+      _handleDebuggerDetach(debuggee, reason);
+      completer.complete(true);
+    }
+  }));
+  return completer.future;
+}
+
+bool isActiveDebugSession(int tabId) =>
+    _debugSessionForTab(tabId, type: TabType.dartApp) != null;
+
+Future<void> clearStaleDebugSession(int tabId) async {
+  final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
+  if (debugSession != null) {
+    await detachDebugger(
+      tabId,
+      type: TabType.dartApp,
+      reason: DetachReason.staleDebugSession,
+    );
   } else {
-    _handleDebuggerDetach(debuggee, reason);
+    await _removeDebugSessionDataInStorage(tabId);
   }
 }
 
+Future<bool> _validateTabIsDebuggable(int dartAppTabId) async {
+  // Check if a debugger is already attached:
+  final existingDebuggerLocation = _debuggerLocation(dartAppTabId);
+  if (existingDebuggerLocation != null) {
+    await _showWarningNotification(
+      'Already debugging in ${existingDebuggerLocation.displayName}.',
+    );
+    return false;
+  }
+  // Determine if this is a Dart app:
+  final debugInfo = await fetchStorageObject<DebugInfo>(
+    type: StorageObject.debugInfo,
+    tabId: dartAppTabId,
+  );
+  if (debugInfo == null) {
+    await _showWarningNotification('Not a Dart app.');
+    return false;
+  }
+  // Determine if there are multiple apps in the tab:
+  final multipleApps = await fetchStorageObject<String>(
+    type: StorageObject.multipleAppsDetected,
+    tabId: dartAppTabId,
+  );
+  if (multipleApps != null) {
+    await _showWarningNotification(
+      'Dart debugging is not supported in a multi-app environment.',
+    );
+    return false;
+  }
+  // Verify that the user is authenticated:
+  final isAuthenticated = await _authenticateUser(dartAppTabId);
+  return isAuthenticated;
+}
+
 void _registerDebugEventListeners() {
   chrome.debugger.onEvent.addListener(allowInterop(_onDebuggerEvent));
-  chrome.debugger.onDetach.addListener(allowInterop(
-    (source, _) => _handleDebuggerDetach(
+  chrome.debugger.onDetach.addListener(allowInterop((source, _) async {
+    await _handleDebuggerDetach(
       source,
       DetachReason.canceledByUser,
-    ),
-  ));
-  chrome.tabs.onRemoved.addListener(allowInterop(
-    (tabId, _) => detachDebugger(
+    );
+  }));
+  chrome.tabs.onRemoved.addListener(allowInterop((tabId, _) async {
+    await detachDebugger(
       tabId,
       type: TabType.devTools,
       reason: DetachReason.devToolsTabClosed,
-    ),
-  ));
+    );
+  }));
 }
 
 _enableExecutionContextReporting(int tabId) {
@@ -154,11 +237,15 @@
 
 Future<void> _onDebuggerEvent(
     Debuggee source, String method, Object? params) async {
+  final tabId = source.tabId;
   maybeForwardMessageToAngularDartDevTools(
       method: method, params: params, tabId: source.tabId);
 
   if (method == 'Runtime.executionContextCreated') {
-    return _maybeConnectToDwds(source.tabId, params);
+    // Only try to connect to DWDS if we don't already have a debugger instance:
+    if (_debuggerLocation(tabId) == null) {
+      return _maybeConnectToDwds(source.tabId, params);
+    }
   }
 
   return _forwardChromeDebuggerEventToDwds(source, method, params);
@@ -176,6 +263,10 @@
   if (debugInfo == null) return;
   if (contextOrigin != debugInfo.appOrigin) return;
   final contextId = context['id'] as int;
+  // Find the correct frame to connect to (this is necessary if the Dart app is
+  // embedded in an IFRAME):
+  final isDartFrame = await _isDartFrame(tabId: tabId, contextId: contextId);
+  if (!isDartFrame) return;
   final connected = await _connectToDwds(
     dartAppContextId: contextId,
     dartAppTabId: tabId,
@@ -183,11 +274,38 @@
   );
   if (!connected) {
     debugWarn('Failed to connect to DWDS for $contextOrigin.');
-    sendConnectFailureMessage(ConnectFailureReason.unknown,
+    await _sendConnectFailureMessage(ConnectFailureReason.unknown,
         dartAppTabId: tabId);
   }
 }
 
+Future<bool> _isDartFrame({required int tabId, required int contextId}) {
+  final completer = Completer<bool>();
+  chrome.debugger.sendCommand(
+      Debuggee(tabId: tabId),
+      'Runtime.evaluate',
+      _InjectedParams(
+          expression:
+              '[window.\$dartAppId, window.\$dartAppInstanceId, window.\$dwdsVersion]',
+          returnByValue: true,
+          contextId: contextId), allowInterop((dynamic response) {
+    final evalResponse = response as _EvalResponse;
+    final value = evalResponse.result.value;
+    final appId = value?[0];
+    final instanceId = value?[1];
+    final dwdsVersion = value?[2];
+    final frameIdentifier = 'Frame at tab $tabId with context $contextId';
+    if (appId == null || instanceId == null) {
+      debugWarn('$frameIdentifier is not a Dart frame.');
+      completer.complete(false);
+    } else {
+      debugLog('Dart $frameIdentifier is using DWDS $dwdsVersion.');
+      completer.complete(true);
+    }
+  }));
+  return completer.future;
+}
+
 Future<bool> _connectToDwds({
   required int dartAppContextId,
   required int dartAppTabId,
@@ -208,16 +326,16 @@
     appTabId: dartAppTabId,
     trigger: trigger,
     onIncoming: (data) => _routeDwdsEvent(data, client, dartAppTabId),
-    onDone: () {
-      detachDebugger(
+    onDone: () async {
+      await detachDebugger(
         dartAppTabId,
         type: TabType.dartApp,
         reason: DetachReason.connectionDoneEvent,
       );
     },
-    onError: (err) {
+    onError: (err) async {
       debugWarn('Connection error: $err', verbose: true);
-      detachDebugger(
+      await detachDebugger(
         dartAppTabId,
         type: TabType.dartApp,
         reason: DetachReason.connectionErrorEvent,
@@ -226,8 +344,10 @@
     cancelOnError: true,
   );
   _debugSessions.add(debugSession);
-  final tabUrl = await _getTabUrl(dartAppTabId);
+  // Create a connection with the lifeline port to keep the debug session alive:
+  await maybeCreateLifelinePort(dartAppTabId);
   // Send a DevtoolsRequest to the event stream:
+  final tabUrl = await _getTabUrl(dartAppTabId);
   debugSession.sendEvent(DevToolsRequest((b) => b
     ..appId = debugInfo.appId
     ..instanceId = debugInfo.appInstanceId
@@ -259,26 +379,33 @@
 
 void _forwardDwdsEventToChromeDebugger(
     ExtensionRequest message, SocketClient client, int tabId) {
-  final messageParams = message.commandParams ?? '{}';
-  final params = BuiltMap<String, Object>(json.decode(messageParams)).toMap();
-  chrome.debugger.sendCommand(
-      Debuggee(tabId: tabId), message.command, js_util.jsify(params),
-      allowInterop(([e]) {
-    // No arguments indicate that an error occurred.
-    if (e == null) {
-      client.sink
-          .add(jsonEncode(serializers.serialize(ExtensionResponse((b) => b
-            ..id = message.id
-            ..success = false
-            ..result = JSON.stringify(chrome.runtime.lastError)))));
-    } else {
-      client.sink
-          .add(jsonEncode(serializers.serialize(ExtensionResponse((b) => b
-            ..id = message.id
-            ..success = true
-            ..result = JSON.stringify(e)))));
-    }
-  }));
+  try {
+    final messageParams = message.commandParams;
+    final params = messageParams == null
+        ? <String, Object>{}
+        : BuiltMap<String, Object>(json.decode(messageParams)).toMap();
+    chrome.debugger.sendCommand(
+        Debuggee(tabId: tabId), message.command, js_util.jsify(params),
+        allowInterop(([e]) {
+      // No arguments indicate that an error occurred.
+      if (e == null) {
+        client.sink
+            .add(jsonEncode(serializers.serialize(ExtensionResponse((b) => b
+              ..id = message.id
+              ..success = false
+              ..result = JSON.stringify(chrome.runtime.lastError)))));
+      } else {
+        client.sink
+            .add(jsonEncode(serializers.serialize(ExtensionResponse((b) => b
+              ..id = message.id
+              ..success = true
+              ..result = JSON.stringify(e)))));
+      }
+    }));
+  } catch (error) {
+    debugError(
+        'Error forwarding ${message.command} with ${message.commandParams} to chrome.debugger: $error');
+  }
 }
 
 void _forwardChromeDebuggerEventToDwds(
@@ -294,7 +421,8 @@
   }
 }
 
-void _openDevTools(String devToolsUri, {required int dartAppTabId}) async {
+Future<void> _openDevTools(String devToolsUri,
+    {required int dartAppTabId}) async {
   if (devToolsUri.isEmpty) {
     debugError('DevTools URI is empty.');
     return;
@@ -317,14 +445,19 @@
     final devToolsOpener = await fetchStorageObject<DevToolsOpener>(
         type: StorageObject.devToolsOpener);
     final devToolsTab = await createTab(
-      devToolsUri,
+      addQueryParameters(
+        devToolsUri,
+        queryParameters: {
+          'ide': 'DebugExtension',
+        },
+      ),
       inNewWindow: devToolsOpener?.newWindow ?? false,
     );
     debugSession.devToolsTabId = devToolsTab.id;
   }
 }
 
-void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
+Future<void> _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
   final tabId = source.tabId;
   debugLog(
     'Debugger detached due to: $reason',
@@ -332,24 +465,42 @@
     prefix: '$tabId',
   );
   final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
-  if (debugSession == null) return;
-  debugLog('Removing debug session...');
-  _removeDebugSession(debugSession);
-  // Notify the extension panels that the debug session has ended:
-  _sendStopDebuggingMessage(reason, dartAppTabId: source.tabId);
-  // Remove the DevTools URI and encoded URI from storage:
-  await removeStorageObject(type: StorageObject.devToolsUri, tabId: tabId);
-  await removeStorageObject(type: StorageObject.encodedUri, tabId: tabId);
-  // Maybe close the associated DevTools tab as well:
-  final devToolsTabId = debugSession.devToolsTabId;
+  if (debugSession != null) {
+    debugLog('Removing debug session...');
+    _removeDebugSession(debugSession);
+    // Notify the extension panels that the debug session has ended:
+    await _sendStopDebuggingMessage(reason, dartAppTabId: tabId);
+    // Maybe close the associated DevTools tab as well:
+    await _maybeCloseDevTools(debugSession.devToolsTabId);
+  }
+  await _removeDebugSessionDataInStorage(tabId);
+}
+
+Future<void> _maybeCloseDevTools(int? devToolsTabId) async {
   if (devToolsTabId == null) return;
   final devToolsTab = await getTab(devToolsTabId);
   if (devToolsTab != null) {
     debugLog('Closing DevTools tab...');
-    chrome.tabs.remove(devToolsTabId);
+    await removeTab(devToolsTabId);
   }
 }
 
+Future<void> _removeDebugSessionDataInStorage(int tabId) async {
+  // Remove the DevTools URI, encoded URI, and multiple apps info from storage:
+  await removeStorageObject(
+    type: StorageObject.devToolsUri,
+    tabId: tabId,
+  );
+  await removeStorageObject(
+    type: StorageObject.encodedUri,
+    tabId: tabId,
+  );
+  await removeStorageObject(
+    type: StorageObject.multipleAppsDetected,
+    tabId: tabId,
+  );
+}
+
 void _removeDebugSession(_DebugSession debugSession) {
   // Note: package:sse will try to keep the connection alive, even after the
   // client has been closed. Therefore the extension sends an event to notify
@@ -361,30 +512,33 @@
   debugSession.sendEvent(event);
   debugSession.close();
   final removed = _debugSessions.remove(debugSession);
-  if (!removed) {
+  if (removed) {
+    // Maybe remove the corresponding lifeline connection:
+    maybeRemoveLifelinePort(debugSession.appTabId);
+  } else {
     debugWarn('Could not remove debug session.');
   }
 }
 
-void sendConnectFailureMessage(ConnectFailureReason reason,
+Future<bool> _sendConnectFailureMessage(ConnectFailureReason reason,
     {required int dartAppTabId}) async {
   final json = jsonEncode(serializers.serialize(ConnectFailure((b) => b
     ..tabId = dartAppTabId
     ..reason = reason.name)));
-  sendRuntimeMessage(
+  return await sendRuntimeMessage(
       type: MessageType.connectFailure,
       body: json,
       sender: Script.background,
       recipient: Script.debuggerPanel);
 }
 
-void _sendStopDebuggingMessage(DetachReason reason,
+Future<bool> _sendStopDebuggingMessage(DetachReason reason,
     {required int dartAppTabId}) async {
   final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
     ..tabId = dartAppTabId
     ..reason = reason.name
     ..newState = DebugStateChange.stopDebugging)));
-  sendRuntimeMessage(
+  return await sendRuntimeMessage(
       type: MessageType.debugStateChange,
       body: json,
       sender: Script.background,
@@ -402,6 +556,85 @@
   }
 }
 
+Future<bool> _authenticateUser(int tabId) async {
+  final isAlreadyAuthenticated = await _fetchIsAuthenticated(tabId);
+  if (isAlreadyAuthenticated) return true;
+  final debugInfo = await fetchStorageObject<DebugInfo>(
+    type: StorageObject.debugInfo,
+    tabId: tabId,
+  );
+  final authUrl = debugInfo?.authUrl ?? _authUrl(debugInfo?.extensionUrl);
+  if (authUrl == null) {
+    await _showWarningNotification('Cannot authenticate user.');
+    return false;
+  }
+  final isAuthenticated = await _sendAuthRequest(authUrl);
+  if (isAuthenticated) {
+    await setStorageObject<String>(
+      type: StorageObject.isAuthenticated,
+      value: '$isAuthenticated',
+      tabId: tabId,
+    );
+  } else {
+    await _sendConnectFailureMessage(
+      ConnectFailureReason.authentication,
+      dartAppTabId: tabId,
+    );
+    await createTab(authUrl, inNewWindow: false);
+  }
+  return isAuthenticated;
+}
+
+Future<bool> _fetchIsAuthenticated(int tabId) async {
+  final authenticated = await fetchStorageObject<String>(
+    type: StorageObject.isAuthenticated,
+    tabId: tabId,
+  );
+  return authenticated == 'true';
+}
+
+Future<bool> _sendAuthRequest(String authUrl) async {
+  final response = await fetchRequest(authUrl);
+  final responseBody = response.body ?? '';
+  return responseBody.contains('Dart Debug Authentication Success!');
+}
+
+Future<bool> _showWarningNotification(String message) {
+  final completer = Completer<bool>();
+  chrome.notifications.create(
+    /*notificationId*/ null,
+    NotificationOptions(
+      title: '[Error] Dart Debug Extension',
+      message: message,
+      iconUrl: 'static_assets/dart.png',
+      type: 'basic',
+    ),
+    allowInterop((_) {
+      completer.complete(true);
+    }),
+  );
+  return completer.future;
+}
+
+DebuggerLocation? _debuggerLocation(int dartAppTabId) {
+  final debugSession = _debugSessionForTab(dartAppTabId, type: TabType.dartApp);
+  final trigger = _tabIdToTrigger[dartAppTabId];
+  if (debugSession == null || trigger == null) return null;
+
+  switch (trigger) {
+    case Trigger.extensionIcon:
+      if (debugSession.devToolsTabId != null) {
+        return DebuggerLocation.dartDevTools;
+      } else {
+        return DebuggerLocation.ide;
+      }
+    case Trigger.angularDartDevTools:
+      return DebuggerLocation.angularDartDevTools;
+    case Trigger.extensionPanel:
+      return DebuggerLocation.chromeDevTools;
+  }
+}
+
 /// Construct an [ExtensionEvent] from [method] and [params].
 ExtensionEvent _extensionEventFor(String method, dynamic params) {
   return ExtensionEvent((b) => b
@@ -475,16 +708,71 @@
   }
 
   void sendEvent<T>(T event) {
-    _socketClient.sink.add(jsonEncode(serializers.serialize(event)));
+    try {
+      _socketClient.sink.add(jsonEncode(serializers.serialize(event)));
+    } catch (error) {
+      debugError('Error sending event $event: $error');
+    }
   }
 
   void sendBatchedEvent(ExtensionEvent event) {
-    _batchController.sink.add(event);
+    try {
+      _batchController.sink.add(event);
+    } catch (error) {
+      debugError('Error sending batched event $event: $error');
+    }
   }
 
   void close() {
-    _socketClient.close();
-    _batchSubscription.cancel();
-    _batchController.close();
+    try {
+      _socketClient.close();
+    } catch (error) {
+      debugError('Error closing socket client: $error');
+    }
+    try {
+      _batchSubscription.cancel();
+    } catch (error) {
+      debugError('Error canceling batch subscription: $error');
+    }
+    try {
+      _batchController.close();
+    } catch (error) {
+      debugError('Error closing batch controller: $error');
+    }
   }
 }
+
+String? _authUrl(String? extensionUrl) {
+  if (extensionUrl == null) return null;
+  final authUrl = Uri.parse(extensionUrl).replace(path: authenticationPath);
+  switch (authUrl.scheme) {
+    case 'ws':
+      return authUrl.replace(scheme: 'http').toString();
+    case 'wss':
+      return authUrl.replace(scheme: 'https').toString();
+    default:
+      return authUrl.toString();
+  }
+}
+
+@JS()
+@anonymous
+class _EvalResponse {
+  external _EvalResult get result;
+}
+
+@JS()
+@anonymous
+class _EvalResult {
+  external List<String?>? get value;
+}
+
+@JS()
+@anonymous
+class _InjectedParams {
+  external String get expresion;
+  external bool get returnByValue;
+  external int get contextId;
+  external factory _InjectedParams(
+      {String? expression, bool? returnByValue, int? contextId});
+}
diff --git a/dwds/debug_extension_mv3/web/detector.dart b/dwds/debug_extension_mv3/web/detector.dart
index 7689983..5490077 100644
--- a/dwds/debug_extension_mv3/web/detector.dart
+++ b/dwds/debug_extension_mv3/web/detector.dart
@@ -5,36 +5,99 @@
 @JS()
 library detector;
 
+import 'dart:convert';
 import 'dart:html';
 import 'dart:js_util';
+
+import 'package:dwds/data/debug_info.dart';
 import 'package:js/js.dart';
 
 import 'chrome_api.dart';
+import 'data_serializers.dart';
 import 'logger.dart';
 import 'messaging.dart';
 
+const _multipleAppsAttribute = 'data-multiple-dart-apps';
+
 void main() {
   _registerListeners();
 }
 
 void _registerListeners() {
   document.addEventListener('dart-app-ready', _onDartAppReadyEvent);
+  document.addEventListener('dart-auth-response', _onDartAuthEvent);
 }
 
-void _onDartAppReadyEvent(Event event) {
+Future<void> _onDartAppReadyEvent(Event event) async {
   final debugInfo = getProperty(event, 'detail') as String?;
   if (debugInfo == null) {
     debugWarn(
         'No debug info sent with ready event, instead reading from Window.');
     _injectDebugInfoScript();
   } else {
-    _sendMessageToBackgroundScript(
+    await _sendMessageToBackgroundScript(
       type: MessageType.debugInfo,
       body: debugInfo,
     );
+    _sendAuthRequest(debugInfo);
+    _detectMultipleDartApps();
   }
 }
 
+Future<void> _onDartAuthEvent(Event event) async {
+  final isAuthenticated = getProperty(event, 'detail') as String?;
+  if (isAuthenticated == null) return;
+  await _sendMessageToBackgroundScript(
+    type: MessageType.isAuthenticated,
+    body: isAuthenticated,
+  );
+}
+
+void _detectMultipleDartApps() {
+  final documentElement = document.documentElement;
+  if (documentElement == null) return;
+
+  if (documentElement.hasAttribute(_multipleAppsAttribute)) {
+    _sendMessageToBackgroundScript(
+      type: MessageType.multipleAppsDetected,
+      body: 'true',
+    );
+    return;
+  }
+
+  final multipleAppsObserver =
+      MutationObserver(_detectMultipleDartAppsCallback);
+  multipleAppsObserver.observe(
+    documentElement,
+    attributeFilter: [_multipleAppsAttribute],
+  );
+}
+
+void _detectMultipleDartAppsCallback(
+  List<dynamic> mutations,
+  MutationObserver observer,
+) {
+  for (var mutation in mutations) {
+    if (_isMultipleAppsMutation(mutation)) {
+      _sendMessageToBackgroundScript(
+        type: MessageType.multipleAppsDetected,
+        body: 'true',
+      );
+      observer.disconnect();
+    }
+  }
+}
+
+bool _isMultipleAppsMutation(dynamic mutation) {
+  final isAttributeMutation = hasProperty(mutation, 'type') &&
+      getProperty(mutation, 'type') == 'attributes';
+  if (isAttributeMutation) {
+    return hasProperty(mutation, 'attributeName') &&
+        getProperty(mutation, 'attributeName') == _multipleAppsAttribute;
+  }
+  return false;
+}
+
 // TODO(elliette): Remove once DWDS 17.0.0 is in Flutter stable. If we are on an
 // older version of DWDS, then the debug info is not sent along with the ready
 // event. Therefore we must read it from the Window object, which is slower.
@@ -46,14 +109,23 @@
   document.head?.append(script);
 }
 
-void _sendMessageToBackgroundScript({
+Future<void> _sendMessageToBackgroundScript({
   required MessageType type,
   required String body,
-}) {
-  sendRuntimeMessage(
+}) async {
+  await sendRuntimeMessage(
     type: type,
     body: body,
     sender: Script.detector,
     recipient: Script.background,
   );
 }
+
+void _sendAuthRequest(String debugInfoJson) {
+  final debugInfo =
+      serializers.deserialize(jsonDecode(debugInfoJson)) as DebugInfo?;
+  final appOrigin = debugInfo?.appOrigin;
+  if (appOrigin != null) {
+    window.postMessage('dart-auth-request', appOrigin);
+  }
+}
diff --git a/dwds/debug_extension_mv3/web/devtools.dart b/dwds/debug_extension_mv3/web/devtools.dart
index a8b37b3..6494eef 100644
--- a/dwds/debug_extension_mv3/web/devtools.dart
+++ b/dwds/debug_extension_mv3/web/devtools.dart
@@ -6,8 +6,9 @@
 library devtools;
 
 import 'dart:html';
-import 'package:js/js.dart';
+
 import 'package:dwds/data/debug_info.dart';
+import 'package:js/js.dart';
 
 import 'chrome_api.dart';
 import 'logger.dart';
@@ -18,7 +19,7 @@
 
 void main() async {
   _registerListeners();
-  _maybeCreatePanels();
+  await _maybeCreatePanels();
 }
 
 void _registerListeners() {
@@ -26,12 +27,11 @@
     Object _,
     String storageArea,
   ) {
-    if (storageArea != 'session') return;
     _maybeCreatePanels();
   }));
 }
 
-void _maybeCreatePanels() async {
+Future<void> _maybeCreatePanels() async {
   if (panelsExist) return;
   final tabId = chrome.devtools.inspectedWindow.tabId;
   final debugInfo = await fetchStorageObject<DebugInfo>(
@@ -43,7 +43,7 @@
   if (!isInternalBuild) return;
   // Create a Debugger panel for all internal apps:
   chrome.devtools.panels.create(
-    isDevMode() ? '[DEV] Dart Debugger' : 'Dart Debugger',
+    isDevMode ? '[DEV] Dart Debugger' : 'Dart Debugger',
     '',
     'static_assets/debugger_panel.html',
     allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
@@ -52,7 +52,7 @@
   final isFlutterApp = debugInfo.isFlutterApp ?? false;
   if (isFlutterApp) {
     chrome.devtools.panels.create(
-      isDevMode() ? '[DEV] Flutter Inspector' : 'Flutter Inspector',
+      isDevMode ? '[DEV] Flutter Inspector' : 'Flutter Inspector',
       '',
       'static_assets/inspector_panel.html',
       allowInterop((ExtensionPanel panel) => _onPanelAdded(panel, debugInfo)),
diff --git a/dwds/debug_extension_mv3/web/lifeline_ports.dart b/dwds/debug_extension_mv3/web/lifeline_ports.dart
index 23f57ed..d43ad9b 100644
--- a/dwds/debug_extension_mv3/web/lifeline_ports.dart
+++ b/dwds/debug_extension_mv3/web/lifeline_ports.dart
@@ -12,24 +12,19 @@
 import 'package:js/js.dart';
 
 import 'chrome_api.dart';
+import 'debug_session.dart';
 import 'logger.dart';
+import 'utils.dart';
 
-// Switch to true to enable debug logs.
-// TODO(elliette): Enable / disable with flag while building the extension.
-final enableDebugLogging = true;
+Port? _lifelinePort;
+int? _lifelineTab;
 
-Port? lifelinePort;
-int? lifelineTab;
-final dartTabs = <int>{};
-
-void maybeCreateLifelinePort(int tabId) {
-  // Keep track of current Dart tabs that are being debugged. This way if one of
-  // them is closed, we can reconnect the lifeline port to another one:
-  dartTabs.add(tabId);
-  debugLog('Dart tabs are: $dartTabs');
+Future<void> maybeCreateLifelinePort(int tabId) async {
+  // This is only necessary for Manifest V3 extensions:
+  if (!isMV3) return;
   // Don't create a lifeline port if we already have one (meaning another Dart
   // app is currently being debugged):
-  if (lifelinePort != null) {
+  if (_lifelinePort != null) {
     debugWarn('Port already exists.');
     return;
   }
@@ -38,32 +33,24 @@
   // Inject the connection script into the current Dart tab, that way the tab
   // will connect to the port:
   debugLog('Creating lifeline port.');
-  lifelineTab = tabId;
-  chrome.scripting.executeScript(
-    InjectDetails(
-      target: Target(tabId: tabId),
-      files: ['lifeline_connection.dart.js'],
-    ),
-    /*callback*/ null,
-  );
+  _lifelineTab = tabId;
+  await injectScript('lifeline_connection.dart.js', tabId: tabId);
 }
 
 void maybeRemoveLifelinePort(int removedTabId) {
-  final removedDartTab = dartTabs.remove(removedTabId);
-  // If the removed tab was not a Dart tab, return early.
-  if (!removedDartTab) return;
-  debugLog('Removed tab $removedTabId, Dart tabs are now $dartTabs.');
+  // This is only necessary for Manifest V3 extensions:
+  if (!isMV3) return;
   // If the removed Dart tab hosted the lifeline port connection, see if there
   // are any other Dart tabs to connect to. Otherwise disconnect the port.
-  if (lifelineTab == removedTabId) {
-    if (dartTabs.isEmpty) {
-      lifelineTab = null;
+  if (_lifelineTab == removedTabId) {
+    if (existsActiveDebugSession) {
+      _lifelineTab = latestAppBeingDebugged;
+      debugLog('Reconnecting lifeline port to a new Dart tab: $_lifelineTab.');
+      _reconnectToLifelinePort();
+    } else {
+      _lifelineTab = null;
       debugLog('No more Dart tabs, disconnecting from lifeline port.');
       _disconnectFromLifelinePort();
-    } else {
-      lifelineTab = dartTabs.last;
-      debugLog('Reconnecting lifeline port to a new Dart tab: $lifelineTab.');
-      _reconnectToLifelinePort();
     }
   }
 }
@@ -71,7 +58,7 @@
 void _keepLifelinePortAlive(Port port) {
   final portName = port.name ?? '';
   if (portName != 'keepAlive') return;
-  lifelinePort = port;
+  _lifelinePort = port;
   // Reconnect to the lifeline port every 5 minutes, as per:
   // https://bugs.chromium.org/p/chromium/issues/detail?id=1146434#c6
   Timer(Duration(minutes: 5), () {
@@ -82,26 +69,26 @@
 
 void _reconnectToLifelinePort() {
   debugLog('Reconnecting...');
-  if (lifelinePort == null) {
+  if (_lifelinePort == null) {
     debugWarn('Could not find a lifeline port.');
     return;
   }
-  if (lifelineTab == null) {
+  if (_lifelineTab == null) {
     debugWarn('Could not find a lifeline tab.');
     return;
   }
   // Disconnect from the port, and then recreate the connection with the current
   // Dart tab:
   _disconnectFromLifelinePort();
-  maybeCreateLifelinePort(lifelineTab!);
+  maybeCreateLifelinePort(_lifelineTab!);
   debugLog('Reconnection complete.');
 }
 
 void _disconnectFromLifelinePort() {
   debugLog('Disconnecting...');
-  if (lifelinePort != null) {
-    lifelinePort!.disconnect();
-    lifelinePort = null;
+  if (_lifelinePort != null) {
+    _lifelinePort!.disconnect();
+    _lifelinePort = null;
     debugLog('Disconnection complete.');
   }
 }
diff --git a/dwds/debug_extension_mv3/web/logger.dart b/dwds/debug_extension_mv3/web/logger.dart
index d0599ec..b418392 100644
--- a/dwds/debug_extension_mv3/web/logger.dart
+++ b/dwds/debug_extension_mv3/web/logger.dart
@@ -45,7 +45,7 @@
   _LogLevel? level,
   String? prefix,
 }) {
-  if (!verbose && !isDevMode()) return;
+  if (!verbose && !isDevMode) return;
   final logMsg = prefix != null ? '[$prefix] $msg' : msg;
   final logLevel = level ?? _LogLevel.info;
   switch (logLevel) {
diff --git a/dwds/debug_extension_mv3/web/manifest_mv2.json b/dwds/debug_extension_mv3/web/manifest_mv2.json
new file mode 100644
index 0000000..5fb9b66
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/manifest_mv2.json
@@ -0,0 +1,31 @@
+{
+  "name": "Dart Debug Extension",
+  "version": "1.33",
+  "manifest_version": 2,
+  "devtools_page": "static_assets/devtools.html",
+  "browser_action": {
+    "default_icon": "static_assets/dart_grey.png"
+  },
+  "externally_connectable": {
+    "ids": ["nbkbficgbembimioedhceniahniffgpl"]
+  },
+  "permissions": [
+    "debugger",
+    "notifications",
+    "storage",
+    "tabs",
+    "webNavigation"
+  ],
+  "background": {
+    "scripts": ["background.dart.js"]
+  },
+  "content_scripts": [
+    {
+      "matches": ["<all_urls>"],
+      "js": ["detector.dart.js"],
+      "run_at": "document_end"
+    }
+  ],
+  "web_accessible_resources": ["debug_info.dart.js"],
+  "options_page": "static_assets/settings.html"
+}
diff --git a/dwds/debug_extension_mv3/web/manifest.json b/dwds/debug_extension_mv3/web/manifest_mv3.json
similarity index 91%
rename from dwds/debug_extension_mv3/web/manifest.json
rename to dwds/debug_extension_mv3/web/manifest_mv3.json
index d16a32e..9c98ad8 100644
--- a/dwds/debug_extension_mv3/web/manifest.json
+++ b/dwds/debug_extension_mv3/web/manifest_mv3.json
@@ -1,10 +1,10 @@
 {
   "name": "Dart Debug Extension",
-  "version": "1.31",
+  "version": "1.33",
   "manifest_version": 3,
   "devtools_page": "static_assets/devtools.html",
   "action": {
-    "default_icon": "static_assets/dart_dev.png"
+    "default_icon": "static_assets/dart_grey.png"
   },
   "externally_connectable": {
     "ids": ["nbkbficgbembimioedhceniahniffgpl"]
diff --git a/dwds/debug_extension_mv3/web/messaging.dart b/dwds/debug_extension_mv3/web/messaging.dart
index 5aa551a..4446bd0 100644
--- a/dwds/debug_extension_mv3/web/messaging.dart
+++ b/dwds/debug_extension_mv3/web/messaging.dart
@@ -5,12 +5,13 @@
 @JS()
 library messaging;
 
+import 'dart:async';
 import 'dart:convert';
 
 import 'package:js/js.dart';
 
-import 'data_serializers.dart';
 import 'chrome_api.dart';
+import 'data_serializers.dart';
 import 'logger.dart';
 
 enum Script {
@@ -24,10 +25,12 @@
 }
 
 enum MessageType {
+  isAuthenticated,
   connectFailure,
   debugInfo,
   debugStateChange,
-  devToolsUrl;
+  devToolsUrl,
+  multipleAppsDetected;
 
   factory MessageType.fromString(String value) {
     return MessageType.values.byName(value);
@@ -87,15 +90,19 @@
         decodedMessage.from != expectedSender) {
       return;
     }
-    messageHandler(
-        serializers.deserialize(jsonDecode(decodedMessage.body)) as T);
+    if (T == String) {
+      messageHandler(decodedMessage.body as T);
+    } else {
+      messageHandler(
+          serializers.deserialize(jsonDecode(decodedMessage.body)) as T);
+    }
   } catch (error) {
     debugError(
         'Error intercepting $expectedType from $expectedSender to $expectedRecipient: $error');
   }
 }
 
-void sendRuntimeMessage(
+Future<bool> sendRuntimeMessage(
     {required MessageType type,
     required String body,
     required Script sender,
@@ -106,10 +113,19 @@
     type: type,
     body: body,
   );
+  final completer = Completer<bool>();
   chrome.runtime.sendMessage(
     /*id*/ null,
     message.toJSON(),
     /*options*/ null,
-    /*callback*/ null,
+    allowInterop(() {
+      final error = chrome.runtime.lastError;
+      if (error != null) {
+        debugError(
+            'Error sending $type to $recipient from $sender: ${error.message}');
+      }
+      completer.complete(error != null);
+    }),
   );
+  return completer.future;
 }
diff --git a/dwds/debug_extension_mv3/web/panel.dart b/dwds/debug_extension_mv3/web/panel.dart
index d2ed9a6..acf420d 100644
--- a/dwds/debug_extension_mv3/web/panel.dart
+++ b/dwds/debug_extension_mv3/web/panel.dart
@@ -5,6 +5,7 @@
 @JS()
 library panel;
 
+import 'dart:async';
 import 'dart:convert';
 import 'dart:html';
 
@@ -18,47 +19,64 @@
 import 'logger.dart';
 import 'messaging.dart';
 import 'storage.dart';
+import 'utils.dart';
 
-bool connecting = false;
-String devToolsBackgroundColor = darkColor;
-bool isDartApp = true;
+bool _connecting = false;
+String _backgroundColor = _darkColor;
+bool _isDartApp = true;
 
-const bugLinkId = 'bugLink';
-const darkColor = '202125';
-const darkThemeClass = 'dark-theme';
-const hiddenClass = 'hidden';
-const iframeContainerId = 'iframeContainer';
-const landingPageId = 'landingPage';
-const launchDebugConnectionButtonId = 'launchDebugConnectionButton';
-const lightColor = 'ffffff';
-const lightThemeClass = 'light-theme';
-const loadingSpinnerId = 'loadingSpinner';
-const panelAttribute = 'data-panel';
-const panelBodyId = 'panelBody';
-const showClass = 'show';
-const warningBannerId = 'warningBanner';
-const warningMsgId = 'warningMsg';
+const _bugLinkId = 'bugLink';
+const _darkColor = '202125';
+const _darkThemeClass = 'dark-theme';
+const _hiddenClass = 'hidden';
+const _iframeContainerId = 'iframeContainer';
+const _landingPageId = 'landingPage';
+const _launchDebugConnectionButtonId = 'launchDebugConnectionButton';
+const _lightColor = 'ffffff';
+const _lightThemeClass = 'light-theme';
+const _loadingSpinnerId = 'loadingSpinner';
+const _panelAttribute = 'data-panel';
+const _panelBodyId = 'panelBody';
+const _showClass = 'show';
+const _warningBannerId = 'warningBanner';
+const _warningMsgId = 'warningMsg';
+
+const _noAppDetectedMsg = 'No app detected.';
+const _lostConnectionMsg = 'Lost connection.';
+const _connectionTimeoutMsg = 'Connection timed out.';
+const _failedToConnectMsg = 'Failed to connect, please try again.';
+const _pleaseAuthenticateMsg = 'Please re-authenticate and try again.';
+const _multipleAppsMsg = 'Cannot debug multiple apps in a page.';
 
 int get _tabId => chrome.devtools.inspectedWindow.tabId;
 
-void main() {
-  _registerListeners();
+Future<void> main() async {
+  unawaited(
+    _registerListeners().catchError((error) {
+      debugWarn('Error registering listeners in panel: $error');
+    }),
+  );
   _setColorThemeToMatchChromeDevTools();
-  _maybeUpdateFileABugLink();
+  await _maybeUpdateFileABugLink();
+  final multipleApps = await fetchStorageObject<String>(
+    type: StorageObject.multipleAppsDetected,
+    tabId: _tabId,
+  );
+  _maybeShowMultipleAppsWarning(multipleApps);
 }
 
-void _registerListeners() {
+Future<void> _registerListeners() async {
   chrome.storage.onChanged.addListener(allowInterop(_handleStorageChanges));
   chrome.runtime.onMessage.addListener(allowInterop(_handleRuntimeMessages));
   final launchDebugConnectionButton =
-      document.getElementById(launchDebugConnectionButtonId) as ButtonElement;
+      document.getElementById(_launchDebugConnectionButtonId) as ButtonElement;
   launchDebugConnectionButton.addEventListener('click', _launchDebugConnection);
 
-  _maybeInjectDevToolsIframe();
+  await _maybeInjectDevToolsIframe();
 }
 
 void _handleRuntimeMessages(
-    dynamic jsRequest, MessageSender sender, Function sendResponse) async {
+    dynamic jsRequest, MessageSender sender, Function sendResponse) {
   if (jsRequest is! String) return;
 
   interceptMessage<DebugStateChange>(
@@ -88,7 +106,7 @@
         if (connectFailure.tabId != _tabId) {
           return;
         }
-        connecting = false;
+        _connecting = false;
         _handleConnectFailure(
           ConnectFailureReason.fromString(connectFailure.reason ?? 'unknown'),
         );
@@ -96,9 +114,6 @@
 }
 
 void _handleStorageChanges(Object storageObj, String storageArea) {
-  // We only care about session storage objects:
-  if (storageArea != 'session') return;
-
   interceptStorageChange<DebugInfo>(
     storageObj: storageObj,
     expectedType: StorageObject.debugInfo,
@@ -111,47 +126,67 @@
     tabId: _tabId,
     changeHandler: _handleDevToolsUriChanges,
   );
+  interceptStorageChange<String>(
+    storageObj: storageObj,
+    expectedType: StorageObject.multipleAppsDetected,
+    tabId: _tabId,
+    changeHandler: _maybeShowMultipleAppsWarning,
+  );
 }
 
-void _handleDebugInfoChanges(DebugInfo? debugInfo) async {
-  if (debugInfo == null && isDartApp) {
-    isDartApp = false;
-    _showWarningBanner('Dart app is no longer open.');
+void _handleDebugInfoChanges(DebugInfo? debugInfo) {
+  if (debugInfo == null && _isDartApp) {
+    _isDartApp = false;
+    if (!_warningBannerIsVisible()) {
+      _showWarningBanner(_noAppDetectedMsg);
+    }
   }
-  if (debugInfo != null && !isDartApp) {
-    isDartApp = true;
-    _hideWarningBanner();
+  if (debugInfo != null && !_isDartApp) {
+    _isDartApp = true;
+    if (_warningBannerIsVisible()) {
+      _hideWarningBanner();
+    }
   }
 }
 
-void _handleDevToolsUriChanges(String? devToolsUri) async {
+void _handleDevToolsUriChanges(String? devToolsUri) {
   if (devToolsUri != null) {
     _injectDevToolsIframe(devToolsUri);
   }
 }
 
-void _maybeUpdateFileABugLink() async {
+void _maybeShowMultipleAppsWarning(String? multipleApps) {
+  if (multipleApps != null) {
+    _showWarningBanner(_multipleAppsMsg);
+  } else {
+    if (_warningBannerIsVisible(message: _multipleAppsMsg)) {
+      _hideWarningBanner();
+    }
+  }
+}
+
+Future<void> _maybeUpdateFileABugLink() async {
   final debugInfo = await fetchStorageObject<DebugInfo>(
     type: StorageObject.debugInfo,
     tabId: _tabId,
   );
   final isInternal = debugInfo?.isInternalBuild ?? false;
   if (isInternal) {
-    final bugLink = document.getElementById(bugLinkId);
+    final bugLink = document.getElementById(_bugLinkId);
     if (bugLink == null) return;
     bugLink.setAttribute(
-        'href', 'http://b/issues/new?component=775375&template=1369639');
+        'href', 'http://b/issues/new?component=775375&template=1791321');
   }
 }
 
-void _setColorThemeToMatchChromeDevTools() async {
+void _setColorThemeToMatchChromeDevTools() {
   final chromeTheme = chrome.devtools.panels.themeName;
-  final panelBody = document.getElementById(panelBodyId);
+  final panelBody = document.getElementById(_panelBodyId);
   if (chromeTheme == 'dark') {
-    devToolsBackgroundColor = darkColor;
+    _backgroundColor = _darkColor;
     _updateColorThemeForElement(panelBody, isDarkTheme: true);
   } else {
-    devToolsBackgroundColor = lightColor;
+    _backgroundColor = _lightColor;
     _updateColorThemeForElement(panelBody, isDarkTheme: false);
   }
 }
@@ -161,10 +196,10 @@
   required bool isDarkTheme,
 }) {
   if (element == null) return;
-  final classToRemove = isDarkTheme ? lightThemeClass : darkThemeClass;
+  final classToRemove = isDarkTheme ? _lightThemeClass : _darkThemeClass;
   if (element.classes.contains(classToRemove)) {
     element.classes.remove(classToRemove);
-    final classToAdd = isDarkTheme ? darkThemeClass : lightThemeClass;
+    final classToAdd = isDarkTheme ? _darkThemeClass : _lightThemeClass;
     element.classes.add(classToAdd);
   }
 }
@@ -172,93 +207,120 @@
 void _handleDebugConnectionLost(String? reason) {
   final detachReason = DetachReason.fromString(reason ?? 'unknown');
   _removeDevToolsIframe();
-  _updateElementVisibility(landingPageId, visible: true);
-  if (detachReason != DetachReason.canceledByUser) {
-    _showWarningBanner('Lost connection.');
+  _updateElementVisibility(_landingPageId, visible: true);
+  switch (detachReason) {
+    case DetachReason.canceledByUser:
+      return;
+    case DetachReason.staleDebugSession:
+    case DetachReason.navigatedAwayFromApp:
+      _showWarningBanner(_noAppDetectedMsg);
+      break;
+    default:
+      _showWarningBanner(_lostConnectionMsg);
+      break;
   }
 }
 
 void _handleConnectFailure(ConnectFailureReason reason) {
   switch (reason) {
     case ConnectFailureReason.authentication:
-      _showWarningBanner('Please re-authenticate and try again.');
+      _showWarningBanner(_pleaseAuthenticateMsg);
       break;
     case ConnectFailureReason.noDartApp:
-      _showWarningBanner('No Dart app detected.');
+      _showWarningBanner(_noAppDetectedMsg);
       break;
     case ConnectFailureReason.timeout:
-      _showWarningBanner('Connection timed out.');
+      _showWarningBanner(_connectionTimeoutMsg);
       break;
     default:
-      _showWarningBanner('Failed to connect, please try again.');
+      _showWarningBanner(_failedToConnectMsg);
   }
-  _updateElementVisibility(launchDebugConnectionButtonId, visible: true);
-  _updateElementVisibility(loadingSpinnerId, visible: false);
+  _updateElementVisibility(_launchDebugConnectionButtonId, visible: true);
+  _updateElementVisibility(_loadingSpinnerId, visible: false);
+}
+
+bool _warningBannerIsVisible({String? message}) {
+  final warningBanner = document.getElementById(_warningBannerId);
+  final isVisible =
+      warningBanner != null && warningBanner.classes.contains(_showClass);
+  if (message == null || isVisible == false) return isVisible;
+  final warningMsg = document.getElementById(_warningMsgId);
+  return warningMsg?.innerHtml == message;
 }
 
 void _showWarningBanner(String message) {
-  final warningMsg = document.getElementById(warningMsgId);
+  final warningMsg = document.getElementById(_warningMsgId);
   warningMsg?.setInnerHtml(message);
-  print(warningMsg);
-  final warningBanner = document.getElementById(warningBannerId);
-  warningBanner?.classes.add(showClass);
+  final warningBanner = document.getElementById(_warningBannerId);
+  warningBanner?.classes.add(_showClass);
 }
 
 void _hideWarningBanner() {
-  final warningBanner = document.getElementById(warningBannerId);
-  warningBanner?.classes.remove(showClass);
+  final warningBanner = document.getElementById(_warningBannerId);
+  warningBanner?.classes.remove(_showClass);
 }
 
-void _launchDebugConnection(Event _) async {
-  _updateElementVisibility(launchDebugConnectionButtonId, visible: false);
-  _updateElementVisibility(loadingSpinnerId, visible: true);
+Future<void> _launchDebugConnection(Event _) async {
+  _updateElementVisibility(_launchDebugConnectionButtonId, visible: false);
+  _updateElementVisibility(_loadingSpinnerId, visible: true);
   final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
     ..tabId = _tabId
     ..newState = DebugStateChange.startDebugging)));
-  sendRuntimeMessage(
+  await sendRuntimeMessage(
       type: MessageType.debugStateChange,
       body: json,
       sender: Script.debuggerPanel,
       recipient: Script.background);
-  _maybeHandleConnectionTimeout();
+  unawaited(_maybeHandleConnectionTimeout().catchError((_) {}));
 }
 
-void _maybeHandleConnectionTimeout() async {
-  connecting = true;
+Future<void> _maybeHandleConnectionTimeout() async {
+  _connecting = true;
   await Future.delayed(Duration(seconds: 10));
-  if (connecting == true) {
+  if (_connecting == true) {
     _handleConnectFailure(ConnectFailureReason.timeout);
   }
 }
 
-void _maybeInjectDevToolsIframe() async {
+Future<void> _maybeInjectDevToolsIframe() async {
   final devToolsUri = await fetchStorageObject<String>(
       type: StorageObject.devToolsUri, tabId: _tabId);
-  if (devToolsUri != null) {
+  if (devToolsUri == null) return;
+  if (isActiveDebugSession(_tabId)) {
+    debugWarn('Unexpected state. Stale DevTools URI.');
+    await clearStaleDebugSession(_tabId);
+    _updateElementVisibility(_landingPageId, visible: true);
+  } else {
     _injectDevToolsIframe(devToolsUri);
   }
 }
 
 void _injectDevToolsIframe(String devToolsUri) {
-  connecting = false;
-  final iframeContainer = document.getElementById(iframeContainerId);
+  _connecting = false;
+  final iframeContainer = document.getElementById(_iframeContainerId);
   if (iframeContainer == null) return;
-  final panelBody = document.getElementById(panelBodyId);
-  final panelType = panelBody?.getAttribute(panelAttribute) ?? 'debugger';
+  final panelBody = document.getElementById(_panelBodyId);
+  final panelType = panelBody?.getAttribute(_panelAttribute) ?? 'debugger';
   final iframe = document.createElement('iframe');
-  iframe.setAttribute(
-    'src',
-    '$devToolsUri&embed=true&page=$panelType&backgroundColor=$devToolsBackgroundColor',
+  final iframeSrc = addQueryParameters(
+    devToolsUri,
+    queryParameters: {
+      'ide': 'ChromeDevTools',
+      'embed': 'true',
+      'page': panelType,
+      'backgroundColor': _backgroundColor,
+    },
   );
+  iframe.setAttribute('src', iframeSrc);
   _hideWarningBanner();
-  _updateElementVisibility(landingPageId, visible: false);
-  _updateElementVisibility(loadingSpinnerId, visible: false);
-  _updateElementVisibility(launchDebugConnectionButtonId, visible: true);
+  _updateElementVisibility(_landingPageId, visible: false);
+  _updateElementVisibility(_loadingSpinnerId, visible: false);
+  _updateElementVisibility(_launchDebugConnectionButtonId, visible: true);
   iframeContainer.append(iframe);
 }
 
 void _removeDevToolsIframe() {
-  final iframeContainer = document.getElementById(iframeContainerId);
+  final iframeContainer = document.getElementById(_iframeContainerId);
   final iframe = iframeContainer?.firstChild;
   if (iframe == null) return;
   iframe.remove();
@@ -268,8 +330,8 @@
   final element = document.getElementById(elementId);
   if (element == null) return;
   if (visible) {
-    element.classes.remove(hiddenClass);
+    element.classes.remove(_hiddenClass);
   } else {
-    element.classes.add(hiddenClass);
+    element.classes.add(_hiddenClass);
   }
 }
diff --git a/dwds/debug_extension_mv3/web/settings.dart b/dwds/debug_extension_mv3/web/settings.dart
index 450b190..d6af1b8 100644
--- a/dwds/debug_extension_mv3/web/settings.dart
+++ b/dwds/debug_extension_mv3/web/settings.dart
@@ -23,7 +23,7 @@
   saveButton.addEventListener('click', _saveSettingsToStorage);
 }
 
-void _updateSettingsFromStorage(Event _) async {
+Future<void> _updateSettingsFromStorage(Event _) async {
   final devToolsOpener = await fetchStorageObject<DevToolsOpener>(
       type: StorageObject.devToolsOpener);
   final openInNewWindow = devToolsOpener?.newWindow ?? false;
@@ -31,7 +31,7 @@
   _getRadioButton('tabOpt').checked = !openInNewWindow;
 }
 
-void _saveSettingsToStorage(Event event) async {
+Future<void> _saveSettingsToStorage(Event event) async {
   event.preventDefault();
   _maybeHideSavedMsg();
   final form = document.querySelector("form") as FormElement;
@@ -48,9 +48,7 @@
   final snackbar = document.getElementById('savedSnackbar');
   if (snackbar == null) return;
   snackbar.classes.add('show');
-  Timer(Duration(seconds: 3), () {
-    _maybeHideSavedMsg();
-  });
+  Timer(Duration(seconds: 3), _maybeHideSavedMsg);
 }
 
 void _maybeHideSavedMsg() {
diff --git a/dwds/debug_extension_mv3/web/static_assets/dart_warning.png b/dwds/debug_extension_mv3/web/static_assets/dart_warning.png
new file mode 100644
index 0000000..9608d0d
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/static_assets/dart_warning.png
Binary files differ
diff --git a/dwds/debug_extension_mv3/web/storage.dart b/dwds/debug_extension_mv3/web/storage.dart
index d748027..ac12958 100644
--- a/dwds/debug_extension_mv3/web/storage.dart
+++ b/dwds/debug_extension_mv3/web/storage.dart
@@ -14,12 +14,15 @@
 import 'chrome_api.dart';
 import 'data_serializers.dart';
 import 'logger.dart';
+import 'utils.dart';
 
 enum StorageObject {
   debugInfo,
   devToolsOpener,
   devToolsUri,
-  encodedUri;
+  encodedUri,
+  isAuthenticated,
+  multipleAppsDetected;
 
   Persistance get persistance {
     switch (this) {
@@ -31,6 +34,10 @@
         return Persistance.sessionOnly;
       case StorageObject.encodedUri:
         return Persistance.sessionOnly;
+      case StorageObject.isAuthenticated:
+        return Persistance.sessionOnly;
+      case StorageObject.multipleAppsDetected:
+        return Persistance.sessionOnly;
     }
   }
 }
@@ -128,6 +135,9 @@
 }
 
 StorageArea _getStorageArea(Persistance persistance) {
+  // MV2 extensions don't have access to session storage:
+  if (!isMV3) return chrome.storage.local;
+
   switch (persistance) {
     case Persistance.acrossSessions:
       return chrome.storage.local;
diff --git a/dwds/debug_extension_mv3/web/utils.dart b/dwds/debug_extension_mv3/web/utils.dart
index c75549e..fe33b65 100644
--- a/dwds/debug_extension_mv3/web/utils.dart
+++ b/dwds/debug_extension_mv3/web/utils.dart
@@ -11,39 +11,155 @@
 import 'package:js/js.dart';
 
 import 'chrome_api.dart';
+import 'logger.dart';
 
-Future<Tab> createTab(String url, {bool inNewWindow = false}) async {
+Future<Tab> createTab(String url, {bool inNewWindow = false}) {
+  final completer = Completer<Tab>();
   if (inNewWindow) {
-    final windowPromise = chrome.windows.create(
+    chrome.windows.create(
       WindowInfo(focused: true, url: url),
+      allowInterop(
+        (WindowObj windowObj) {
+          completer.complete(windowObj.tabs.first);
+        },
+      ),
     );
-    final windowObj = await promiseToFuture<WindowObj>(windowPromise);
-    return windowObj.tabs.first;
+  } else {
+    chrome.tabs.create(
+      TabInfo(
+        active: true,
+        url: url,
+      ),
+      allowInterop(completer.complete),
+    );
   }
-  final tabPromise = chrome.tabs.create(TabInfo(
-    active: true,
-    url: url,
-  ));
-  return promiseToFuture<Tab>(tabPromise);
+  return completer.future;
 }
 
 Future<Tab?> getTab(int tabId) {
-  return promiseToFuture<Tab?>(chrome.tabs.get(tabId));
+  final completer = Completer<Tab?>();
+  chrome.tabs.get(tabId, allowInterop(completer.complete));
+  return completer.future;
 }
 
-Future<Tab?> getActiveTab() async {
+Future<Tab?> get activeTab async {
+  final completer = Completer<Tab?>();
   final query = QueryInfo(active: true, currentWindow: true);
-  final tabs = List<Tab>.from(await promiseToFuture(chrome.tabs.query(query)));
-  return tabs.isNotEmpty ? tabs.first : null;
+  chrome.tabs.query(query, allowInterop((List tabs) {
+    if (tabs.isNotEmpty) {
+      completer.complete(tabs.first as Tab);
+    } else {
+      completer.complete(null);
+    }
+  }));
+  return completer.future;
+}
+
+Future<bool> removeTab(int tabId) {
+  final completer = Completer<bool>();
+  chrome.tabs.remove(tabId, allowInterop(() {
+    completer.complete(true);
+  }));
+  return completer.future;
+}
+
+Future<bool> injectScript(String scriptName, {required int tabId}) async {
+  if (isMV3) {
+    await promiseToFuture(_executeScriptMV3(_InjectDetails(
+      target: Target(tabId: tabId),
+      files: [scriptName],
+    )));
+    return true;
+  } else {
+    debugWarn('Script injection is only supported in Manifest V3.');
+    return false;
+  }
+}
+
+void onExtensionIconClicked(void Function(Tab) callback) {
+  if (isMV3) {
+    _onExtensionIconClickedMV3(callback);
+  } else {
+    _onExtensionIconClickedMV2(callback);
+  }
+}
+
+void setExtensionIcon(IconInfo info) {
+  if (isMV3) {
+    _setExtensionIconMV3(info, /*callback*/ null);
+  } else {
+    _setExtensionIconMV2(info, /*callback*/ null);
+  }
 }
 
 bool? _isDevMode;
 
-bool isDevMode() {
+bool get isDevMode {
   if (_isDevMode != null) {
     return _isDevMode!;
   }
   final extensionManifest = chrome.runtime.getManifest();
   final extensionName = getProperty(extensionManifest, 'name') ?? '';
-  return extensionName.contains('DEV');
+  final isDevMode = extensionName.contains('DEV');
+  _isDevMode = isDevMode;
+  return isDevMode;
+}
+
+bool? _isMV3;
+
+bool get isMV3 {
+  if (_isMV3 != null) {
+    return _isMV3!;
+  }
+  final extensionManifest = chrome.runtime.getManifest();
+  final manifestVersion =
+      getProperty(extensionManifest, 'manifest_version') ?? 2;
+  final isMV3 = manifestVersion == 3;
+  _isMV3 = isMV3;
+  return isMV3;
+}
+
+String addQueryParameters(
+  String uri, {
+  required Map<String, String> queryParameters,
+}) {
+  final originalUri = Uri.parse(uri);
+  final newUri = originalUri.replace(queryParameters: {
+    ...originalUri.queryParameters,
+    ...queryParameters,
+  });
+  return newUri.toString();
+}
+
+@JS('chrome.browserAction.onClicked.addListener')
+external void _onExtensionIconClickedMV2(void Function(Tab tab) callback);
+
+@JS('chrome.action.onClicked.addListener')
+external void _onExtensionIconClickedMV3(void Function(Tab tab) callback);
+
+@JS('chrome.browserAction.setIcon')
+external void _setExtensionIconMV2(IconInfo iconInfo, Function? callback);
+
+@JS('chrome.action.setIcon')
+external void _setExtensionIconMV3(IconInfo iconInfo, Function? callback);
+
+@JS()
+@anonymous
+class IconInfo {
+  external String get path;
+  external factory IconInfo({required String path});
+}
+
+@JS('chrome.scripting.executeScript')
+external Object _executeScriptMV3(_InjectDetails details);
+
+@JS()
+@anonymous
+class _InjectDetails {
+  external Target get target;
+  external List<String>? get files;
+  external factory _InjectDetails({
+    Target target,
+    List<String>? files,
+  });
 }
diff --git a/dwds/debug_extension_mv3/web/web_api.dart b/dwds/debug_extension_mv3/web/web_api.dart
index 1676fed..4453b18 100644
--- a/dwds/debug_extension_mv3/web/web_api.dart
+++ b/dwds/debug_extension_mv3/web/web_api.dart
@@ -2,9 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 import 'dart:html';
+import 'dart:js_util' as js_util;
 
 import 'package:js/js.dart';
-import 'dart:js_util' as js_util;
 
 @JS()
 // ignore: non_constant_identifier_names
diff --git a/dwds/lib/dart_web_debug_service.dart b/dwds/lib/dart_web_debug_service.dart
index 5dce5f3..4a44877 100644
--- a/dwds/lib/dart_web_debug_service.dart
+++ b/dwds/lib/dart_web_debug_service.dart
@@ -4,25 +4,23 @@
 
 import 'dart:async';
 
+import 'package:dwds/data/build_result.dart';
+import 'package:dwds/src/connections/app_connection.dart';
+import 'package:dwds/src/connections/debug_connection.dart';
+import 'package:dwds/src/events.dart';
+import 'package:dwds/src/handlers/dev_handler.dart';
+import 'package:dwds/src/handlers/injector.dart';
+import 'package:dwds/src/handlers/socket_connections.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/servers/devtools.dart';
+import 'package:dwds/src/servers/extension_backend.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf.dart';
 import 'package:sse/server/sse_handler.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import 'data/build_result.dart';
-import 'src/connections/app_connection.dart';
-import 'src/connections/debug_connection.dart';
-import 'src/events.dart';
-import 'src/handlers/dev_handler.dart';
-import 'src/handlers/injector.dart';
-import 'src/handlers/socket_connections.dart';
-import 'src/loaders/strategy.dart';
-import 'src/readers/asset_reader.dart';
-import 'src/servers/devtools.dart';
-import 'src/servers/extension_backend.dart';
-import 'src/services/expression_compiler.dart';
-import 'src/utilities/sdk_configuration.dart';
-
 typedef ConnectionProvider = Future<ChromeConnection> Function();
 
 /// The Dart Web Debug Service.
@@ -84,13 +82,12 @@
     bool enableDevtoolsLaunch = true,
     DevtoolsLauncher? devtoolsLauncher,
     bool launchDevToolsInNewWindow = true,
-    SdkConfigurationProvider sdkConfigurationProvider =
-        const DefaultSdkConfigurationProvider(),
     bool emitDebugEvents = true,
     bool isInternalBuild = false,
-    bool isFlutterApp = false,
+    Future<bool> Function()? isFlutterApp,
   }) async {
     globalLoadStrategy = loadStrategy;
+    isFlutterApp ??= () => Future.value(true);
 
     DevTools? devTools;
     Future<String>? extensionUri;
@@ -148,7 +145,6 @@
       injected,
       spawnDds,
       launchDevToolsInNewWindow,
-      sdkConfigurationProvider,
     );
 
     return Dwds._(
diff --git a/dwds/lib/data/debug_info.dart b/dwds/lib/data/debug_info.dart
index 288d1e7..d1f9eb5 100644
--- a/dwds/lib/data/debug_info.dart
+++ b/dwds/lib/data/debug_info.dart
@@ -19,8 +19,10 @@
   String? get appInstanceId;
   String? get appOrigin;
   String? get appUrl;
+  String? get authUrl;
   String? get dwdsVersion;
   String? get extensionUrl;
   bool? get isInternalBuild;
   bool? get isFlutterApp;
+  String? get tabUrl;
 }
diff --git a/dwds/lib/data/debug_info.g.dart b/dwds/lib/data/debug_info.g.dart
index 3807db2..85a444f 100644
--- a/dwds/lib/data/debug_info.g.dart
+++ b/dwds/lib/data/debug_info.g.dart
@@ -54,6 +54,13 @@
         ..add(serializers.serialize(value,
             specifiedType: const FullType(String)));
     }
+    value = object.authUrl;
+    if (value != null) {
+      result
+        ..add('authUrl')
+        ..add(serializers.serialize(value,
+            specifiedType: const FullType(String)));
+    }
     value = object.dwdsVersion;
     if (value != null) {
       result
@@ -82,6 +89,13 @@
         ..add(
             serializers.serialize(value, specifiedType: const FullType(bool)));
     }
+    value = object.tabUrl;
+    if (value != null) {
+      result
+        ..add('tabUrl')
+        ..add(serializers.serialize(value,
+            specifiedType: const FullType(String)));
+    }
     return result;
   }
 
@@ -116,6 +130,10 @@
           result.appUrl = serializers.deserialize(value,
               specifiedType: const FullType(String)) as String?;
           break;
+        case 'authUrl':
+          result.authUrl = serializers.deserialize(value,
+              specifiedType: const FullType(String)) as String?;
+          break;
         case 'dwdsVersion':
           result.dwdsVersion = serializers.deserialize(value,
               specifiedType: const FullType(String)) as String?;
@@ -132,6 +150,10 @@
           result.isFlutterApp = serializers.deserialize(value,
               specifiedType: const FullType(bool)) as bool?;
           break;
+        case 'tabUrl':
+          result.tabUrl = serializers.deserialize(value,
+              specifiedType: const FullType(String)) as String?;
+          break;
       }
     }
 
@@ -151,6 +173,8 @@
   @override
   final String? appUrl;
   @override
+  final String? authUrl;
+  @override
   final String? dwdsVersion;
   @override
   final String? extensionUrl;
@@ -158,6 +182,8 @@
   final bool? isInternalBuild;
   @override
   final bool? isFlutterApp;
+  @override
+  final String? tabUrl;
 
   factory _$DebugInfo([void Function(DebugInfoBuilder)? updates]) =>
       (new DebugInfoBuilder()..update(updates))._build();
@@ -168,10 +194,12 @@
       this.appInstanceId,
       this.appOrigin,
       this.appUrl,
+      this.authUrl,
       this.dwdsVersion,
       this.extensionUrl,
       this.isInternalBuild,
-      this.isFlutterApp})
+      this.isFlutterApp,
+      this.tabUrl})
       : super._();
 
   @override
@@ -190,10 +218,12 @@
         appInstanceId == other.appInstanceId &&
         appOrigin == other.appOrigin &&
         appUrl == other.appUrl &&
+        authUrl == other.authUrl &&
         dwdsVersion == other.dwdsVersion &&
         extensionUrl == other.extensionUrl &&
         isInternalBuild == other.isInternalBuild &&
-        isFlutterApp == other.isFlutterApp;
+        isFlutterApp == other.isFlutterApp &&
+        tabUrl == other.tabUrl;
   }
 
   @override
@@ -204,10 +234,12 @@
     _$hash = $jc(_$hash, appInstanceId.hashCode);
     _$hash = $jc(_$hash, appOrigin.hashCode);
     _$hash = $jc(_$hash, appUrl.hashCode);
+    _$hash = $jc(_$hash, authUrl.hashCode);
     _$hash = $jc(_$hash, dwdsVersion.hashCode);
     _$hash = $jc(_$hash, extensionUrl.hashCode);
     _$hash = $jc(_$hash, isInternalBuild.hashCode);
     _$hash = $jc(_$hash, isFlutterApp.hashCode);
+    _$hash = $jc(_$hash, tabUrl.hashCode);
     _$hash = $jf(_$hash);
     return _$hash;
   }
@@ -220,10 +252,12 @@
           ..add('appInstanceId', appInstanceId)
           ..add('appOrigin', appOrigin)
           ..add('appUrl', appUrl)
+          ..add('authUrl', authUrl)
           ..add('dwdsVersion', dwdsVersion)
           ..add('extensionUrl', extensionUrl)
           ..add('isInternalBuild', isInternalBuild)
-          ..add('isFlutterApp', isFlutterApp))
+          ..add('isFlutterApp', isFlutterApp)
+          ..add('tabUrl', tabUrl))
         .toString();
   }
 }
@@ -253,6 +287,10 @@
   String? get appUrl => _$this._appUrl;
   set appUrl(String? appUrl) => _$this._appUrl = appUrl;
 
+  String? _authUrl;
+  String? get authUrl => _$this._authUrl;
+  set authUrl(String? authUrl) => _$this._authUrl = authUrl;
+
   String? _dwdsVersion;
   String? get dwdsVersion => _$this._dwdsVersion;
   set dwdsVersion(String? dwdsVersion) => _$this._dwdsVersion = dwdsVersion;
@@ -270,6 +308,10 @@
   bool? get isFlutterApp => _$this._isFlutterApp;
   set isFlutterApp(bool? isFlutterApp) => _$this._isFlutterApp = isFlutterApp;
 
+  String? _tabUrl;
+  String? get tabUrl => _$this._tabUrl;
+  set tabUrl(String? tabUrl) => _$this._tabUrl = tabUrl;
+
   DebugInfoBuilder();
 
   DebugInfoBuilder get _$this {
@@ -280,10 +322,12 @@
       _appInstanceId = $v.appInstanceId;
       _appOrigin = $v.appOrigin;
       _appUrl = $v.appUrl;
+      _authUrl = $v.authUrl;
       _dwdsVersion = $v.dwdsVersion;
       _extensionUrl = $v.extensionUrl;
       _isInternalBuild = $v.isInternalBuild;
       _isFlutterApp = $v.isFlutterApp;
+      _tabUrl = $v.tabUrl;
       _$v = null;
     }
     return this;
@@ -311,10 +355,12 @@
             appInstanceId: appInstanceId,
             appOrigin: appOrigin,
             appUrl: appUrl,
+            authUrl: authUrl,
             dwdsVersion: dwdsVersion,
             extensionUrl: extensionUrl,
             isInternalBuild: isInternalBuild,
-            isFlutterApp: isFlutterApp);
+            isFlutterApp: isFlutterApp,
+            tabUrl: tabUrl);
     replace(_$result);
     return _$result;
   }
diff --git a/dwds/lib/data/devtools_request.dart b/dwds/lib/data/devtools_request.dart
index d4e87b4..e5c4843 100644
--- a/dwds/lib/data/devtools_request.dart
+++ b/dwds/lib/data/devtools_request.dart
@@ -40,10 +40,11 @@
   /// correct `dartAppInstanceId` automatically.
   String? get tabUrl;
 
-  /// If this is a uri only request.
+  /// Designates this as a request to send back the DevTools URI instead of
+  /// opening DevTools in a new tab or window.
   ///
-  /// Only available on requests coming from dart debug extension.
-  /// If true, DevTools should open in an embedded Chrome DevTools tab.
+  /// Only available on requests coming from the Dart Debug Extension. Is `null`
+  /// for local debug service.
   bool? get uriOnly;
 }
 
diff --git a/dwds/lib/sdk_configuration.dart b/dwds/lib/sdk_configuration.dart
new file mode 100644
index 0000000..1135d37
--- /dev/null
+++ b/dwds/lib/sdk_configuration.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2023, 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.
+
+export 'src/utilities/sdk_configuration.dart'
+    show
+        SdkLayout,
+        SdkConfiguration,
+        SdkConfigurationProvider,
+        DefaultSdkConfigurationProvider;
diff --git a/dwds/lib/shared/README.md b/dwds/lib/shared/README.md
new file mode 100644
index 0000000..ac348e6
--- /dev/null
+++ b/dwds/lib/shared/README.md
@@ -0,0 +1,4 @@
+# Shared directory
+
+The files in this directory are shared between DWDS and external packages (e.g.,
+the Dart Debug Extension).
diff --git a/dwds/lib/src/utilities/batched_stream.dart b/dwds/lib/shared/batched_stream.dart
similarity index 94%
rename from dwds/lib/src/utilities/batched_stream.dart
rename to dwds/lib/shared/batched_stream.dart
index 6da6465..6bbc17c 100644
--- a/dwds/lib/src/utilities/batched_stream.dart
+++ b/dwds/lib/shared/batched_stream.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 import 'package:async/async.dart';
+import 'package:dwds/src/utilities/shared.dart';
 
 /// Stream controller allowing to batch events.
 class BatchedStreamController<T> {
@@ -28,7 +29,7 @@
         _inputController = StreamController<T>(),
         _outputController = StreamController<List<T>>() {
     _inputQueue = StreamQueue<T>(_inputController.stream);
-    unawaited(_batchAndSendEvents());
+    safeUnawaited(_batchAndSendEvents());
   }
 
   /// Sink collecting events.
@@ -38,8 +39,8 @@
   Stream<List<T>> get stream => _outputController.stream;
 
   /// Close the controller.
-  Future<dynamic> close() async {
-    unawaited(_inputController.close());
+  Future<dynamic> close() {
+    safeUnawaited(_inputController.close());
     return _completer.future.then((value) => _outputController.close());
   }
 
diff --git a/dwds/lib/src/connections/app_connection.dart b/dwds/lib/src/connections/app_connection.dart
index fc19384..754d515 100644
--- a/dwds/lib/src/connections/app_connection.dart
+++ b/dwds/lib/src/connections/app_connection.dart
@@ -5,10 +5,11 @@
 import 'dart:async';
 import 'dart:convert';
 
-import '../../data/connect_request.dart';
-import '../../data/run_request.dart';
-import '../../data/serializers.dart';
-import '../handlers/socket_connections.dart';
+import 'package:dwds/data/connect_request.dart';
+import 'package:dwds/data/run_request.dart';
+import 'package:dwds/data/serializers.dart';
+import 'package:dwds/src/handlers/socket_connections.dart';
+import 'package:dwds/src/utilities/shared.dart';
 
 /// A connection between the application loaded in the browser and DWDS.
 class AppConnection {
@@ -19,7 +20,7 @@
   final SocketConnection _connection;
 
   AppConnection(this.request, this._connection) {
-    unawaited(_connection.sink.done.then((v) => _doneCompleter.complete()));
+    safeUnawaited(_connection.sink.done.then((v) => _doneCompleter.complete()));
   }
 
   bool get isInKeepAlivePeriod => _connection.isInKeepAlivePeriod;
diff --git a/dwds/lib/src/connections/debug_connection.dart b/dwds/lib/src/connections/debug_connection.dart
index 594e610..97de4c4 100644
--- a/dwds/lib/src/connections/debug_connection.dart
+++ b/dwds/lib/src/connections/debug_connection.dart
@@ -4,11 +4,10 @@
 
 import 'dart:async';
 
+import 'package:dwds/src/services/app_debug_services.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart';
 import 'package:vm_service/vm_service.dart';
 
-import '../services/app_debug_services.dart';
-import '../services/chrome_proxy_service.dart';
-
 /// A debug connection between the application in the browser and DWDS.
 ///
 /// Supports debugging your running application through the Dart VM Service
diff --git a/dwds/lib/src/debugging/classes.dart b/dwds/lib/src/debugging/classes.dart
index c87bb19..e9f24aa 100644
--- a/dwds/lib/src/debugging/classes.dart
+++ b/dwds/lib/src/debugging/classes.dart
@@ -1,16 +1,15 @@
 // Copyright (c) 2019, 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';
+// BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/debugging/metadata/class.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/services/chrome_debug_exception.dart';
+import 'package:dwds/src/utilities/domain.dart';
+import 'package:dwds/src/utilities/shared.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../../src/services/chrome_debug_exception.dart';
-import '../loaders/strategy.dart';
-import '../utilities/domain.dart';
-import '../utilities/shared.dart';
-import 'metadata/class.dart';
-
 /// Keeps track of Dart classes available in the running application.
 class ClassHelper extends Domain {
   /// Map of class ID to [Class].
@@ -192,7 +191,7 @@
     });
     final fieldRefs = <FieldRef>[];
     final fieldDescriptors = classDescriptor['fields'] as Map<String, dynamic>;
-    fieldDescriptors.forEach((name, descriptor) async {
+    fieldDescriptors.forEach((name, descriptor) {
       final classMetaData = ClassMetaData(
           jsName: descriptor['classRefName'],
           libraryId: descriptor['classRefLibraryId'],
@@ -215,7 +214,7 @@
 
     final staticFieldDescriptors =
         classDescriptor['staticFields'] as Map<String, dynamic>;
-    staticFieldDescriptors.forEach((name, descriptor) async {
+    staticFieldDescriptors.forEach((name, descriptor) {
       fieldRefs.add(
         FieldRef(
           name: name,
diff --git a/dwds/lib/src/debugging/dart_scope.dart b/dwds/lib/src/debugging/dart_scope.dart
index 294bf04..f929d97 100644
--- a/dwds/lib/src/debugging/dart_scope.dart
+++ b/dwds/lib/src/debugging/dart_scope.dart
@@ -2,14 +2,23 @@
 // 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:dwds/src/debugging/debugger.dart';
+import 'package:dwds/src/utilities/objects.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../utilities/objects.dart';
-import 'debugger.dart';
+/// The regular expressions used to filter out temp variables.
+/// Needs to be kept in sync with SDK repo.
+///
+/// TODO(annagrin) - use an alternative way to identify
+/// synthetic variables.
+/// Issue: https://github.com/dart-lang/sdk/issues/44262
+final ddcTemporaryVariableRegExp = RegExp(r'^t(\$[0-9]*)+\w*$');
+final ddcTemporaryTypeVariableRegExp = RegExp(r'^__t[\$\w*]+$');
 
-// TODO(sdk/issues/44262) - use an alternative way to identify synthetic
-// variables.
-final ddcTemporaryVariableRegExp = RegExp(r'^(t[0-9]+\$?[0-9]*|__t[\$\w*]+)$');
+/// Temporary variable regex before SDK changes for patterns.
+/// TODO(annagrin): remove after dart 3.0 is stable.
+final previousDdcTemporaryVariableRegExp =
+    RegExp(r'^(t[0-9]+\$?[0-9]*|__t[\$\w*]+)$');
 
 /// Find the visible Dart properties from a JS Scope Chain, coming from the
 /// scopeChain attribute of a Chrome CallFrame corresponding to [frame].
@@ -66,7 +75,9 @@
     // Dart generic function, where the type arguments get passed in as
     // parameters. Hide those.
     return (type == 'function' && description.startsWith('class ')) ||
-        (ddcTemporaryVariableRegExp.hasMatch(name)) ||
+        previousDdcTemporaryVariableRegExp.hasMatch(name) ||
+        ddcTemporaryVariableRegExp.hasMatch(name) ||
+        ddcTemporaryTypeVariableRegExp.hasMatch(name) ||
         (type == 'object' && description == 'dart.LegacyType.new');
   });
 
diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart
index 0f3f754..88914ab 100644
--- a/dwds/lib/src/debugging/debugger.dart
+++ b/dwds/lib/src/debugging/debugger.dart
@@ -5,25 +5,26 @@
 import 'dart:async';
 import 'dart:math' as math;
 
+import 'package:dwds/src/debugging/dart_scope.dart';
+import 'package:dwds/src/debugging/frame_computer.dart';
+import 'package:dwds/src/debugging/location.dart';
+import 'package:dwds/src/debugging/metadata/class.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/debugging/skip_list.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/services/chrome_debug_exception.dart';
+import 'package:dwds/src/utilities/conversions.dart';
+import 'package:dwds/src/utilities/dart_uri.dart';
+import 'package:dwds/src/utilities/domain.dart';
+import 'package:dwds/src/utilities/objects.dart' show Property;
+import 'package:dwds/src/utilities/server.dart';
+import 'package:dwds/src/utilities/shared.dart';
 import 'package:dwds/src/utilities/synchronized.dart';
 import 'package:logging/logging.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
     hide StackTrace;
 
-import '../loaders/strategy.dart';
-import '../services/chrome_debug_exception.dart';
-import '../utilities/conversions.dart';
-import '../utilities/dart_uri.dart';
-import '../utilities/domain.dart';
-import '../utilities/objects.dart' show Property;
-import '../utilities/shared.dart';
-import 'dart_scope.dart';
-import 'frame_computer.dart';
-import 'location.dart';
-import 'remote_debugger.dart';
-import 'skip_list.dart';
-
 /// Adds [event] to the stream with [streamId] if there is anybody listening
 /// on that stream.
 typedef StreamNotify = void Function(String streamId, Event event);
@@ -82,6 +83,7 @@
   FrameComputer? stackComputer;
 
   bool _isStepping = false;
+  DartLocation? _previousSteppingLocation;
 
   void updateInspector(AppInspectorInterface appInspector) {
     inspector = appInspector;
@@ -142,6 +144,7 @@
       }
     } else {
       _isStepping = false;
+      _previousSteppingLocation = null;
       result = await _remoteDebugger.resume();
     }
     handleErrorIfPresent(result);
@@ -213,7 +216,7 @@
   Future<void> resumeFromStart() => _resumeHandler(null);
 
   /// Notify the debugger the [Isolate] is paused at the application start.
-  void notifyPausedAtStart() async {
+  void notifyPausedAtStart() {
     stackComputer = FrameComputer(this, []);
   }
 
@@ -353,7 +356,12 @@
     final url = urlForScriptId(scriptId);
     if (url == null) return null;
 
-    return _locations.locationForJs(url, line, column);
+    final loc = await _locations.locationForJs(url, line, column);
+    if (loc == null || loc.dartLocation == _previousSteppingLocation) {
+      return null;
+    }
+    _previousSteppingLocation = loc.dartLocation;
+    return loc;
   }
 
   /// Returns script ID for the paused event.
@@ -367,7 +375,7 @@
     // TODO(alanknight): Can these be moved to dart_scope.dart?
     final properties = await visibleProperties(debugger: this, frame: frame);
     final boundVariables = await Future.wait(
-      properties.map((property) async => await _boundVariable(property)),
+      properties.map(_boundVariable),
     );
 
     // Filter out variables that do not come from dart code, such as native
@@ -403,6 +411,36 @@
     return null;
   }
 
+  static bool _isEmptyRange({
+    required int length,
+    int? offset,
+    int? count,
+  }) {
+    if (count == 0) return true;
+    if (offset == null) return false;
+    return offset >= length;
+  }
+
+  static bool _isSubRange({
+    required int length,
+    int? offset,
+    int? count,
+  }) {
+    if (offset == 0 && count == null) return false;
+    return offset != null || count != null;
+  }
+
+  /// Compute the last possible element index in the range of [offset]..end
+  /// that includes [count] elements, if available.
+  static int? _calculateRangeEnd(
+          {int? count, required int offset, required int length}) =>
+      count == null ? null : math.min(offset + count, length);
+
+  /// Calculate the number of available elements in the range.
+  static int _calculateRangeCount(
+          {int? count, required int offset, required int length}) =>
+      count == null ? length - offset : math.min(count, length - offset);
+
   /// Find a sub-range of the entries for a Map/List when offset and/or count
   /// have been specified on a getObject request.
   ///
@@ -410,15 +448,17 @@
   /// will just return a RemoteObject for it and ignore [offset], [count] and
   /// [length]. If it is, then [length] should be the number of entries in the
   /// List/Map and [offset] and [count] should indicate the desired range.
-  Future<RemoteObject> _subrange(
-      String id, int offset, int? count, int length) async {
+  Future<RemoteObject> _subRange(String id,
+      {required int offset, int? count, required int length}) async {
     // TODO(#809): Sometimes we already know the type of the object, and
     // we could take advantage of that to short-circuit.
     final receiver = remoteObjectFor(id);
-    final end = count == null ? null : math.min(offset + count, length);
-    final actualCount = count ?? length - offset;
+    final end =
+        _calculateRangeEnd(count: count, offset: offset, length: length);
+    final rangeCount =
+        _calculateRangeCount(count: count, offset: offset, length: length);
     final args =
-        [offset, actualCount, end].map(dartIdFor).map(remoteObjectFor).toList();
+        [offset, rangeCount, end].map(dartIdFor).map(remoteObjectFor).toList();
     // If this is a List, just call sublist. If it's a Map, get the entries, but
     // avoid doing a toList on a large map using skip/take to get the section we
     // want. To make those alternatives easier in JS, pass both count and end.
@@ -469,12 +509,23 @@
   /// Symbol(DartClass.actualName) and will need to be converted. For a system
   /// List or Map, [offset] and/or [count] can be provided to indicate a desired
   /// range of entries. They will be ignored if there is no [length].
-  Future<List<Property>> getProperties(String objectId,
-      {int? offset, int? count, int? length}) async {
+  Future<List<Property>> getProperties(
+    String objectId, {
+    int? offset,
+    int? count,
+    int? length,
+  }) async {
     String rangeId = objectId;
-    if (length != null && (offset != null || count != null)) {
-      final range = await _subrange(objectId, offset ?? 0, count ?? 0, length);
-      rangeId = range.objectId ?? rangeId;
+    // Ignore offset/count if there is no length:
+    if (length != null) {
+      if (_isEmptyRange(offset: offset, count: count, length: length)) {
+        return [];
+      }
+      if (_isSubRange(offset: offset, count: count, length: length)) {
+        final range = await _subRange(objectId,
+            offset: offset ?? 0, count: count, length: length);
+        rangeId = range.objectId ?? rangeId;
+      }
     }
     final jsProperties = await sendCommandAndValidateResult<List>(
       _remoteDebugger,
@@ -570,10 +621,9 @@
       if (e.data is Map<String, dynamic>) {
         final map = e.data as Map<String, dynamic>;
         if (map['type'] == 'object') {
-          // The className here is generally 'DartError'.
           final obj = RemoteObject(map);
           exception = await inspector.instanceRefFor(obj);
-          if (exception != null && isNativeJsObject(exception)) {
+          if (exception != null && isNativeJsError(exception)) {
             if (obj.description != null) {
               // Create a string exception object.
               final description =
@@ -616,7 +666,7 @@
           // TODO(grouma) - In the future we should send all previously computed
           // skipLists.
           await _remoteDebugger.stepInto(params: {
-            'skipList': await _skipLists.compute(
+            'skipList': _skipLists.compute(
               scriptId,
               await _locations.locationsForUrl(url),
             )
@@ -744,18 +794,21 @@
   return result;
 }
 
+/// Returns true for objects we display for the user.
 bool isDisplayableObject(Object? object) =>
-    object is Sentinel || object is InstanceRef && !isNativeJsObject(object);
+    object is Sentinel ||
+    object is InstanceRef &&
+        !isNativeJsObject(object) &&
+        !isNativeJsError(object);
 
+/// Returns true for non-dart JavaScript objects.
 bool isNativeJsObject(InstanceRef instanceRef) {
-  // New type representation of JS objects reifies them to a type suffixed with
-  // JavaScriptObject.
-  final className = instanceRef.classRef?.name;
-  return (className != null &&
-          className.endsWith('JavaScriptObject') &&
-          instanceRef.classRef?.library?.uri == 'dart:_interceptors') ||
-      // Old type representation still needed to support older SDK versions.
-      className == 'NativeJavaScriptObject';
+  return isNativeJsObjectRef(instanceRef.classRef);
+}
+
+/// Returns true of JavaScript exceptions.
+bool isNativeJsError(InstanceRef instanceRef) {
+  return instanceRef.classRef == classRefForNativeJsError;
 }
 
 /// Returns the Dart line number for the provided breakpoint.
diff --git a/dwds/lib/src/debugging/execution_context.dart b/dwds/lib/src/debugging/execution_context.dart
index 157c4f4..b8639d0 100644
--- a/dwds/lib/src/debugging/execution_context.dart
+++ b/dwds/lib/src/debugging/execution_context.dart
@@ -5,10 +5,9 @@
 import 'dart:async';
 
 import 'package:async/async.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
 import 'package:logging/logging.dart';
 
-import 'remote_debugger.dart';
-
 abstract class ExecutionContext {
   /// Returns the context ID that contains the running Dart application.
   Future<int> get id;
diff --git a/dwds/lib/src/debugging/frame_computer.dart b/dwds/lib/src/debugging/frame_computer.dart
index 00d7ca2..2453880 100644
--- a/dwds/lib/src/debugging/frame_computer.dart
+++ b/dwds/lib/src/debugging/frame_computer.dart
@@ -2,12 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/debugging/debugger.dart';
 import 'package:dwds/src/utilities/synchronized.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import 'debugger.dart';
-
 class FrameComputer {
   final Debugger debugger;
 
diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart
index 083a0ef..9b6e926 100644
--- a/dwds/lib/src/debugging/inspector.dart
+++ b/dwds/lib/src/debugging/inspector.dart
@@ -1,29 +1,28 @@
 // Copyright (c) 2019, 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';
+// BSD-style license that can be found in the LICENSE file.
 
 import 'package:async/async.dart';
 import 'package:collection/collection.dart';
+import 'package:dwds/src/connections/app_connection.dart';
+import 'package:dwds/src/debugging/classes.dart';
+import 'package:dwds/src/debugging/debugger.dart';
+import 'package:dwds/src/debugging/execution_context.dart';
+import 'package:dwds/src/debugging/instance.dart';
+import 'package:dwds/src/debugging/libraries.dart';
+import 'package:dwds/src/debugging/location.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/utilities/conversions.dart';
+import 'package:dwds/src/utilities/dart_uri.dart';
+import 'package:dwds/src/utilities/domain.dart';
+import 'package:dwds/src/utilities/server.dart';
+import 'package:dwds/src/utilities/shared.dart';
 import 'package:logging/logging.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../connections/app_connection.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../utilities/conversions.dart';
-import '../utilities/dart_uri.dart';
-import '../utilities/domain.dart';
-import '../utilities/sdk_configuration.dart';
-import '../utilities/shared.dart';
-import 'classes.dart';
-import 'debugger.dart';
-import 'execution_context.dart';
-import 'instance.dart';
-import 'libraries.dart';
-import 'location.dart';
-import 'remote_debugger.dart';
-
 /// An inspector for a running Dart application contained in the
 /// [WipConnection].
 ///
@@ -75,7 +74,6 @@
 
   /// The root URI from which the application is served.
   final String _root;
-  final SdkConfiguration _sdkConfiguration;
 
   /// JavaScript expression that evaluates to the Dart stack trace mapper.
   static const stackTraceMapperExpression = '\$dartStackTraceUtility.mapper';
@@ -98,7 +96,6 @@
     this._locations,
     this._root,
     this._executionContext,
-    this._sdkConfiguration,
   ) : _isolateRef = _toIsolateRef(_isolate);
 
   Future<void> initialize(
@@ -116,10 +113,9 @@
 
     final scripts = await scriptRefs;
 
-    await DartUri.initialize(_sdkConfiguration);
-    await DartUri.recordAbsoluteUris(
-        libraries.map((lib) => lib.uri).whereNotNull());
-    await DartUri.recordAbsoluteUris(
+    await DartUri.initialize();
+    DartUri.recordAbsoluteUris(libraries.map((lib) => lib.uri).whereNotNull());
+    DartUri.recordAbsoluteUris(
         scripts.map((script) => script.uri).whereNotNull());
 
     isolate.extensionRPCs?.addAll(await _getExtensionRpcs());
@@ -140,7 +136,6 @@
     String root,
     Debugger debugger,
     ExecutionContext executionContext,
-    SdkConfiguration sdkConfiguration,
   ) async {
     final id = createId();
     final time = DateTime.now().millisecondsSinceEpoch;
@@ -176,7 +171,6 @@
       locations,
       root,
       executionContext,
-      sdkConfiguration,
     );
 
     debugger.updateInspector(inspector);
diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart
index 4f41d91..c1f4dcb 100644
--- a/dwds/lib/src/debugging/instance.dart
+++ b/dwds/lib/src/debugging/instance.dart
@@ -4,20 +4,22 @@
 
 import 'dart:math';
 
+import 'package:dwds/src/debugging/debugger.dart';
+import 'package:dwds/src/debugging/metadata/class.dart';
+import 'package:dwds/src/debugging/metadata/function.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/utilities/conversions.dart';
+import 'package:dwds/src/utilities/domain.dart';
+import 'package:dwds/src/utilities/objects.dart';
+import 'package:dwds/src/utilities/shared.dart';
+import 'package:logging/logging.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../loaders/strategy.dart';
-import '../utilities/conversions.dart';
-import '../utilities/domain.dart';
-import '../utilities/objects.dart';
-import '../utilities/shared.dart';
-import 'debugger.dart';
-import 'metadata/class.dart';
-import 'metadata/function.dart';
-
 /// Contains a set of methods for getting [Instance]s and [InstanceRef]s.
 class InstanceHelper extends Domain {
+  final _logger = Logger('InstanceHelper');
+
   InstanceHelper(AppInspectorInterface appInspector, this.debugger) {
     inspector = appInspector;
   }
@@ -77,7 +79,7 @@
       ..offset = (truncated ? offset : null);
   }
 
-  Future<Instance?> _closureInstanceFor(RemoteObject remoteObject) async {
+  Instance? _closureInstanceFor(RemoteObject remoteObject) {
     final objectId = remoteObject.objectId;
     if (objectId == null) return null;
     final result = Instance(
@@ -113,20 +115,33 @@
 
     final classRef = metaData?.classRef;
     if (metaData == null || classRef == null) return null;
-    if (metaData.jsName == 'Function') {
+    if (metaData.isFunction) {
       return _closureInstanceFor(remoteObject);
     }
 
-    final properties = await debugger.getProperties(objectId,
-        offset: offset, count: count, length: metaData.length);
     if (metaData.isSystemList) {
-      return await _listInstanceFor(
-          classRef, remoteObject, properties, offset, count);
+      return await _listInstanceFor(classRef, remoteObject,
+          offset: offset, count: count, length: metaData.length);
     } else if (metaData.isSystemMap) {
-      return await _mapInstanceFor(
-          classRef, remoteObject, properties, offset, count);
+      return await _mapInstanceFor(classRef, remoteObject,
+          offset: offset, count: count, length: metaData.length);
+    } else if (metaData.isRecord) {
+      return await _recordInstanceFor(classRef, remoteObject,
+          offset: offset, count: count, length: metaData.length);
+    } else if (metaData.isSet) {
+      return await _setInstanceFor(
+        classRef,
+        remoteObject,
+        offset: offset,
+        count: count,
+        length: metaData.length,
+      );
+    } else if (metaData.isNativeError) {
+      return await _plainInstanceFor(classRefForNativeJsError, remoteObject,
+          offset: offset, count: count, length: metaData.length);
     } else {
-      return await _plainInstanceFor(classRef, remoteObject, properties);
+      return await _plainInstanceFor(classRef, remoteObject,
+          offset: offset, count: count, length: metaData.length);
     }
   }
 
@@ -152,6 +167,7 @@
   Future<BoundField> _fieldFor(Property property, ClassRef classRef) async {
     final instance = await _instanceRefForRemote(property.value);
     return BoundField(
+        name: property.name,
         decl: FieldRef(
           // TODO(grouma) - Convert JS name to Dart.
           name: property.name,
@@ -172,10 +188,18 @@
 
   /// Create a plain instance of [classRef] from [remoteObject] and the JS
   /// properties [properties].
-  Future<Instance?> _plainInstanceFor(ClassRef classRef,
-      RemoteObject remoteObject, List<Property> properties) async {
+  Future<Instance?> _plainInstanceFor(
+    ClassRef classRef,
+    RemoteObject remoteObject, {
+    int? offset,
+    int? count,
+    int? length,
+  }) async {
     final objectId = remoteObject.objectId;
     if (objectId == null) return null;
+
+    final properties = await debugger.getProperties(objectId,
+        offset: offset, count: count, length: length);
     final dartProperties = await _dartFieldsFor(properties, remoteObject);
     var boundFields = await Future.wait(
         dartProperties.map<Future<BoundField>>((p) => _fieldFor(p, classRef)));
@@ -201,8 +225,14 @@
   }
 
   /// The associations for a Dart Map or IdentityMap.
-  Future<List<MapAssociation>> _mapAssociations(
-      RemoteObject map, int? offset, int? count) async {
+  ///
+  /// Returns a range of [count] associations, if available, starting from
+  /// the [offset].
+  ///
+  /// If [offset] is `null`, assumes 0 offset.
+  /// If [count] is `null`, return all fields starting from the offset.
+  Future<List<MapAssociation>> _mapAssociations(RemoteObject map,
+      {int? offset, int? count}) async {
     // We do this in in awkward way because we want the keys and values, but we
     // can't return things by value or some Dart objects will come back as
     // values that we need to be RemoteObject, e.g. a List of int.
@@ -241,19 +271,29 @@
   }
 
   /// Create a Map instance with class [classRef] from [remoteObject].
+  ///
+  /// Returns an instance containing [count] associations, if available,
+  /// starting from the [offset].
+  ///
+  /// If [offset] is `null`, assumes 0 offset.
+  /// If [count] is `null`, return all fields starting from the offset.
+  /// [length] is the expected length of the whole object, read from
+  /// the [ClassMetaData].
   Future<Instance?> _mapInstanceFor(
-      ClassRef classRef,
-      RemoteObject remoteObject,
-      List<Property> _,
-      int? offset,
-      int? count) async {
+    ClassRef classRef,
+    RemoteObject remoteObject, {
+    int? offset,
+    int? count,
+    int? length,
+  }) async {
     final objectId = remoteObject.objectId;
     if (objectId == null) return null;
+
     // Maps are complicated, do an eval to get keys and values.
-    final associations = await _mapAssociations(remoteObject, offset, count);
-    final length = (offset == null && count == null)
-        ? associations.length
-        : (await instanceRefFor(remoteObject))?.length;
+    final associations =
+        await _mapAssociations(remoteObject, offset: offset, count: count);
+    final rangeCount = _calculateRangeCount(
+        count: count, elementCount: associations.length, length: length);
     return Instance(
         identityHashCode: remoteObject.objectId.hashCode,
         kind: InstanceKind.kMap,
@@ -261,50 +301,264 @@
         classRef: classRef)
       ..length = length
       ..offset = offset
-      ..count = (associations.length == length) ? null : associations.length
+      ..count = rangeCount
       ..associations = associations;
   }
 
-  /// Create a List instance of [classRef] from [remoteObject] with the JS
-  /// properties [properties].
+  /// Create a List instance of [classRef] from [remoteObject].
+  ///
+  /// Returns an instance containing [count] elements, if available,
+  /// starting from the [offset].
+  ///
+  /// If [offset] is `null`, assumes 0 offset.
+  /// If [count] is `null`, return all fields starting from the offset.
+  /// [length] is the expected length of the whole object, read from
+  /// the [ClassMetaData].
   Future<Instance?> _listInstanceFor(
-      ClassRef classRef,
-      RemoteObject remoteObject,
-      List<Property> properties,
-      int? offset,
-      int? count) async {
+    ClassRef classRef,
+    RemoteObject remoteObject, {
+    int? offset,
+    int? count,
+    int? length,
+  }) async {
     final objectId = remoteObject.objectId;
     if (objectId == null) return null;
 
-    /// TODO(annagrin): split into cases to make the logic clear.
-    /// TODO(annagrin): make sure we use offset correctly.
-    final numberOfProperties = _lengthOf(properties) ?? 0;
-    final length = (offset == null && count == null)
-        ? numberOfProperties
-        : (await instanceRefFor(remoteObject))?.length;
-    final indexed = properties.sublist(
-        0, min(count ?? length ?? numberOfProperties, numberOfProperties));
-    final fields = await Future.wait(indexed
-        .map((property) async => await _instanceRefForRemote(property.value)));
+    final elements = await _listElements(remoteObject,
+        offset: offset, count: count, length: length);
+    final rangeCount = _calculateRangeCount(
+        count: count, elementCount: elements.length, length: length);
     return Instance(
         identityHashCode: remoteObject.objectId.hashCode,
         kind: InstanceKind.kList,
         id: objectId,
         classRef: classRef)
       ..length = length
-      ..elements = fields
+      ..elements = elements
       ..offset = offset
-      ..count = (numberOfProperties == length) ? null : numberOfProperties;
+      ..count = rangeCount;
   }
 
-  /// Return the value of the length attribute from [properties], if present.
+  /// The elements for a Dart List.
   ///
-  /// This is only applicable to Lists or Maps, where we expect a length
-  /// attribute. Even if a plain instance happens to have a length field, we
-  /// don't use it to determine the properties to display.
-  int? _lengthOf(List<Property> properties) {
-    final lengthProperty = properties.firstWhere((p) => p.name == 'length');
-    return lengthProperty.value?.value as int?;
+  /// Returns a range of [count] elements, if available, starting from
+  /// the [offset].
+  ///
+  /// If [offset] is `null`, assumes 0 offset.
+  /// If [count] is `null`, return all fields starting from the offset.
+  /// [length] is the expected length of the whole object, read from
+  /// the [ClassMetaData].
+  Future<List<InstanceRef?>> _listElements(
+    RemoteObject list, {
+    int? offset,
+    int? count,
+    int? length,
+  }) async {
+    // Filter out all non-indexed properties
+    final elements = _indexedListProperties(await debugger.getProperties(
+        list.objectId!,
+        offset: offset,
+        count: count,
+        length: length));
+
+    final rangeCount = _calculateRangeCount(
+        count: count, elementCount: elements.length, length: length);
+    final range = elements.sublist(0, rangeCount);
+
+    return Future.wait(
+        range.map((element) => _instanceRefForRemote(element.value)));
+  }
+
+  /// Return elements of the list from [properties].
+  ///
+  /// Ignore any non-elements like 'length', 'fixed$length', etc.
+  static List<Property> _indexedListProperties(List<Property> properties) =>
+      properties
+          .where((p) => p.name != null && int.tryParse(p.name!) != null)
+          .toList();
+
+  /// The fields for a Dart Record.
+  ///
+  /// Returns a range of [count] fields, if available, starting from
+  /// the [offset].
+  ///
+  /// If [offset] is `null`, assumes 0 offset.
+  /// If [count] is `null`, return all fields starting from the offset.
+  Future<List<BoundField>> _recordFields(RemoteObject record,
+      {int? offset, int? count}) async {
+    // We do this in in awkward way because we want the keys and values, but we
+    // can't return things by value or some Dart objects will come back as
+    // values that we need to be RemoteObject, e.g. a List of int.
+    final expression = '''
+      function() {
+        var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart;
+        var shape = sdkUtils.dloadRepl(this, "shape");
+        var positionalCount = sdkUtils.dloadRepl(shape, "positionals");
+        var named = sdkUtils.dloadRepl(shape, "named");
+        named = named == null? null: sdkUtils.dsendRepl(named, "toList", []);
+        var values = sdkUtils.dloadRepl(this, "values");
+        values = sdkUtils.dsendRepl(values, "toList", []);
+
+        return {
+          positionalCount: positionalCount,
+          named: named,
+          values: values
+        };
+      }
+    ''';
+    final result = await inspector.jsCallFunctionOn(record, expression, []);
+    final positionalCountObject =
+        await inspector.loadField(result, 'positionalCount');
+    if (positionalCountObject == null || positionalCountObject.value is! int) {
+      _logger.warning(
+          'Unexpected positional count from record: $positionalCountObject');
+      return [];
+    }
+
+    final namedObject = await inspector.loadField(result, 'named');
+    final valuesObject = await inspector.loadField(result, 'values');
+
+    // Collect positional fields in the requested range.
+    final positionalCount = positionalCountObject.value as int;
+    final positionalOffset = offset ?? 0;
+    final positionalAvailable =
+        _remainingCount(positionalOffset, positionalCount);
+    final positionalRangeCount =
+        min(positionalAvailable, count ?? positionalAvailable);
+    final positionalElements = [
+      for (var i = positionalOffset + 1;
+          i <= positionalOffset + positionalRangeCount;
+          i++)
+        i
+    ];
+
+    // Collect named fields in the requested range.
+    // Account for already collected positional fields.
+    final namedRangeOffset =
+        offset == null ? null : _remainingCount(positionalCount, offset);
+    final namedRangeCount =
+        count == null ? null : _remainingCount(positionalRangeCount, count);
+    final namedInstance = await instanceFor(namedObject,
+        offset: namedRangeOffset, count: namedRangeCount);
+    final namedElements =
+        namedInstance?.elements?.map((e) => e.valueAsString) ?? [];
+
+    final fieldNameElements = [
+      ...positionalElements,
+      ...namedElements,
+    ];
+
+    final valuesInstance =
+        await instanceFor(valuesObject, offset: offset, count: count);
+    final valueElements = valuesInstance?.elements ?? [];
+
+    if (fieldNameElements.length != valueElements.length) {
+      _logger.warning('Record fields and values are not the same length.');
+      return [];
+    }
+
+    final fields = <BoundField>[];
+    Map.fromIterables(fieldNameElements, valueElements).forEach((key, value) {
+      fields.add(BoundField(name: key, value: value));
+    });
+    return fields;
+  }
+
+  static int _remainingCount(int collected, int requested) {
+    return requested < collected ? 0 : requested - collected;
+  }
+
+  /// Create a Record instance with class [classRef] from [remoteObject].
+  ///
+  /// Returns an instance containing [count] fields, if available,
+  /// starting from the [offset].
+  ///
+  /// If [offset] is `null`, assumes 0 offset.
+  /// If [count] is `null`, return all fields starting from the offset.
+  /// [length] is the expected length of the whole object, read from
+  /// the [ClassMetaData].
+  Future<Instance?> _recordInstanceFor(
+    ClassRef classRef,
+    RemoteObject remoteObject, {
+    int? offset,
+    int? count,
+    int? length,
+  }) async {
+    final objectId = remoteObject.objectId;
+    if (objectId == null) return null;
+    // Records are complicated, do an eval to get names and values.
+    final fields =
+        await _recordFields(remoteObject, offset: offset, count: count);
+    final rangeCount = _calculateRangeCount(
+        count: count, elementCount: fields.length, length: length);
+    return Instance(
+        identityHashCode: remoteObject.objectId.hashCode,
+        kind: InstanceKind.kRecord,
+        id: objectId,
+        classRef: classRef)
+      ..length = length
+      ..offset = offset
+      ..count = rangeCount
+      ..fields = fields;
+  }
+
+  Future<Instance?> _setInstanceFor(
+    ClassRef classRef,
+    RemoteObject remoteObject, {
+    int? offset,
+    int? count,
+    int? length,
+  }) async {
+    final objectId = remoteObject.objectId;
+    if (objectId == null) return null;
+
+    final expression = '''
+      function() {
+        const sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart;
+        const jsSet = sdkUtils.dloadRepl(this, "_map");
+        const entries = [...jsSet.values()];
+        return {
+          entries: entries
+        };
+      }
+    ''';
+
+    final result =
+        await inspector.jsCallFunctionOn(remoteObject, expression, []);
+    final entriesObject = await inspector.loadField(result, 'entries');
+    final entriesInstance =
+        await instanceFor(entriesObject, offset: offset, count: count);
+    final elements = entriesInstance?.elements ?? [];
+
+    final setInstance = Instance(
+        identityHashCode: remoteObject.objectId.hashCode,
+        kind: InstanceKind.kSet,
+        id: objectId,
+        classRef: classRef)
+      ..length = length
+      ..elements = elements;
+    if (offset != null && offset > 0) {
+      setInstance.offset = offset;
+    }
+    if (length != null && elements.length < length) {
+      setInstance.count = elements.length;
+    }
+
+    return setInstance;
+  }
+
+  /// Return the available count of elements in the requested range.
+  /// Return `null` if the range includes the whole object.
+  /// [count] is the range length requested by the `getObject` call.
+  /// [elementCount] is the number of elements in the runtime object.
+  /// [length] is the expected length of the whole object, read from
+  /// the [ClassMetaData].
+  static int? _calculateRangeCount(
+      {int? count, int? elementCount, int? length}) {
+    if (count == null) return null;
+    if (elementCount == null) return null;
+    if (length == elementCount) return null;
+    return min(count, elementCount);
   }
 
   /// Filter [allJsProperties] and return a list containing only those
@@ -424,6 +678,30 @@
               classRef: metaData.classRef)
             ..length = metaData.length;
         }
+        if (metaData.isRecord) {
+          return InstanceRef(
+              kind: InstanceKind.kRecord,
+              id: objectId,
+              identityHashCode: remoteObject.objectId.hashCode,
+              classRef: metaData.classRef)
+            ..length = metaData.length;
+        }
+        if (metaData.isSet) {
+          return InstanceRef(
+              kind: InstanceKind.kSet,
+              id: objectId,
+              identityHashCode: remoteObject.objectId.hashCode,
+              classRef: metaData.classRef)
+            ..length = metaData.length;
+        }
+        if (metaData.isNativeError) {
+          return InstanceRef(
+              kind: InstanceKind.kPlainInstance,
+              id: objectId,
+              identityHashCode: remoteObject.objectId.hashCode,
+              classRef: classRefForNativeJsError)
+            ..length = metaData.length;
+        }
         return InstanceRef(
             kind: InstanceKind.kPlainInstance,
             id: objectId,
diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart
index 14f8901..bed9938 100644
--- a/dwds/lib/src/debugging/libraries.dart
+++ b/dwds/lib/src/debugging/libraries.dart
@@ -1,17 +1,16 @@
 // Copyright (c) 2019, 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';
+// BSD-style license that can be found in the LICENSE file.
 
 import 'package:collection/collection.dart';
+import 'package:dwds/src/debugging/metadata/class.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/services/chrome_debug_exception.dart';
+import 'package:dwds/src/utilities/domain.dart';
 import 'package:logging/logging.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../loaders/strategy.dart';
-import '../utilities/domain.dart';
-import '../services/chrome_debug_exception.dart';
-import 'metadata/class.dart';
-
 /// Keeps track of Dart libraries available in the running application.
 class LibraryHelper extends Domain {
   final Logger _logger = Logger('LibraryHelper');
diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart
index 7d3d9d7..2958884 100644
--- a/dwds/lib/src/debugging/location.dart
+++ b/dwds/lib/src/debugging/location.dart
@@ -3,16 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:async/async.dart';
+import 'package:dwds/src/debugging/modules.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/utilities/dart_uri.dart';
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as p;
 import 'package:source_maps/parser.dart';
 import 'package:source_maps/source_maps.dart';
 
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../utilities/dart_uri.dart';
-import 'modules.dart';
-
 var _startTokenId = 1337;
 
 /// A source location, with both Dart and JS information.
@@ -76,6 +75,19 @@
   }
 
   @override
+  int get hashCode => Object.hashAll([uri, line, column]);
+
+  @override
+  bool operator ==(Object? other) {
+    if (other is! DartLocation) {
+      return false;
+    }
+    return uri.serverPath == other.uri.serverPath &&
+        line == other.line &&
+        column == other.column;
+  }
+
+  @override
   String toString() => '[${uri.serverPath}:$line:$column]';
 
   factory DartLocation.fromZeroBased(DartUri uri, int line, int column) =>
@@ -271,11 +283,10 @@
   /// [module] refers to the JS path of a DDC module without the extension.
   ///
   /// This will populate the [_sourceToLocation] and [_moduleToLocations] maps.
-  Future<Set<Location>> _locationsForModule(String module) async {
-    final memoizer =
-        _locationMemoizer.putIfAbsent(module, () => AsyncMemoizer());
+  Future<Set<Location>> _locationsForModule(String module) {
+    final memoizer = _locationMemoizer.putIfAbsent(module, AsyncMemoizer.new);
 
-    return await memoizer.runOnce(() async {
+    return memoizer.runOnce(() async {
       if (_moduleToLocations.containsKey(module)) {
         return _moduleToLocations[module]!;
       }
diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart
index a2638eb..3f9a5c0 100644
--- a/dwds/lib/src/debugging/metadata/class.dart
+++ b/dwds/lib/src/debugging/metadata/class.dart
@@ -1,34 +1,77 @@
 // 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';
+// BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/services/chrome_debug_exception.dart';
 import 'package:dwds/src/utilities/domain.dart';
+import 'package:logging/logging.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../../debugging/remote_debugger.dart';
-import '../../loaders/strategy.dart';
-import '../../services/chrome_debug_exception.dart';
+const _dartCoreLibrary = 'dart:core';
+const _dartInterceptorsLibrary = 'dart:_interceptors';
 
 /// A hard-coded ClassRef for the Closure class.
-final classRefForClosure = classRefFor('dart:core', 'Closure');
+final classRefForClosure = classRefFor(_dartCoreLibrary, 'Closure');
 
 /// A hard-coded ClassRef for the String class.
-final classRefForString = classRefFor('dart:core', InstanceKind.kString);
+final classRefForString = classRefFor(_dartCoreLibrary, InstanceKind.kString);
 
 /// A hard-coded ClassRef for a (non-existent) class called Unknown.
-final classRefForUnknown = classRefFor('dart:core', 'Unknown');
+final classRefForUnknown = classRefFor(_dartCoreLibrary, 'Unknown');
+
+/// A hard-coded ClassRef for a JS exception.
+///
+/// Exceptions are instances of NativeError and its subtypes.
+/// We detect their common base type in class metadata and replace their
+/// classRef by hard-coded reference in instances and instance refs.
+///
+/// TODO(annagrin): this breaks on name changes for JS types.
+/// https://github.com/dart-lang/sdk/issues/51583
+final classRefForNativeJsError =
+    classRefFor(_dartInterceptorsLibrary, 'NativeError');
+
+/// Returns true for non-dart JavaScript classes.
+///
+/// TODO(annagrin): this breaks on name changes for JS types.
+/// https://github.com/dart-lang/sdk/issues/51583
+bool isNativeJsObjectRef(ClassRef? classRef) {
+  final className = classRef?.name;
+  final libraryUri = classRef?.library?.uri;
+  // Non-dart JS objects are all instances of JavaScriptObject
+  // and its subtypes with names that end with 'JavaScriptObject'.
+  return className != null &&
+      libraryUri == _dartInterceptorsLibrary &&
+      className.endsWith('JavaScriptObject');
+}
+
+///  A hard-coded LibraryRef for a a dart:core library.
+final libraryRefForCore = LibraryRef(
+  id: _dartCoreLibrary,
+  name: _dartCoreLibrary,
+  uri: _dartCoreLibrary,
+);
+
+/// Returns a [LibraryRef] for the provided library ID and class name.
+LibraryRef libraryRefFor(String libraryId) => LibraryRef(
+      id: libraryId,
+      name: libraryId,
+      uri: libraryId,
+    );
 
 /// Returns a [ClassRef] for the provided library ID and class name.
-ClassRef classRefFor(String? libraryId, String? name) => ClassRef(
-    id: 'classes|$libraryId|$name',
-    name: name,
-    library: libraryId == null
-        ? null
-        : LibraryRef(id: libraryId, name: libraryId, uri: libraryId));
+ClassRef classRefFor(String libraryId, String? name) => ClassRef(
+      id: 'classes|$libraryId|$name',
+      name: name,
+      library: libraryRefFor(libraryId),
+    );
 
 /// Meta data for a remote Dart class in Chrome.
 class ClassMetaData {
+  static final _logger = Logger('ClassMetadata');
+
   /// The name of the JS constructor for the object.
   ///
   /// This may be a constructor for a Dart, but it's still a JS name. For
@@ -44,15 +87,37 @@
   final String? dartName;
 
   /// The library identifier, which is the URI of the library.
-  final String? libraryId;
+  final String libraryId;
 
-  factory ClassMetaData(
-      {Object? jsName, Object? libraryId, Object? dartName, Object? length}) {
-    return ClassMetaData._(jsName as String?, libraryId as String?,
-        dartName as String?, int.tryParse('$length'));
+  factory ClassMetaData({
+    Object? jsName,
+    Object? libraryId,
+    Object? dartName,
+    Object? length,
+    bool isFunction = false,
+    bool isRecord = false,
+    bool isNativeError = false,
+  }) {
+    return ClassMetaData._(
+      jsName as String?,
+      libraryId as String? ?? _dartCoreLibrary,
+      dartName as String?,
+      int.tryParse('$length'),
+      isFunction,
+      isRecord,
+      isNativeError,
+    );
   }
 
-  ClassMetaData._(this.jsName, this.libraryId, this.dartName, this.length);
+  ClassMetaData._(
+    this.jsName,
+    this.libraryId,
+    this.dartName,
+    this.length,
+    this.isFunction,
+    this.isRecord,
+    this.isNativeError,
+  );
 
   /// Returns the ID of the class.
   ///
@@ -67,17 +132,36 @@
     try {
       final evalExpression = '''
       function(arg) {
-        const sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart;
-        const classObject = sdkUtils.getReifiedType(arg);
-        const isFunction = sdkUtils.AbstractFunctionType.is(classObject);
+        const sdk = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk');
+        const dart = sdk.dart;
+        const interceptors = sdk._interceptors;
+        const classObject = dart.getReifiedType(arg);
+        const isFunction = classObject instanceof dart.AbstractFunctionType;
+        const isRecord = classObject instanceof dart.RecordType;
+        const isNativeError = dart.is(arg, interceptors.NativeError);
         const result = {};
-        result['name'] = isFunction ? 'Function' : classObject.name;
-        result['libraryId'] = sdkUtils.getLibraryUri(classObject);
-        result['dartName'] = sdkUtils.typeName(classObject);
+        var name = isFunction ? 'Function' : classObject.name;
+
+        result['name'] = name;
+        result['libraryId'] = dart.getLibraryUri(classObject);
+        result['dartName'] = dart.typeName(classObject);
+        result['isFunction'] = isFunction;
+        result['isRecord'] = isRecord;
+        result['isNativeError'] = isNativeError;
         result['length'] = arg['length'];
+
+        if (isRecord) {
+          result['name'] = 'Record';
+          var shape = classObject.shape;
+          var positionalCount = shape.positionals;
+          var namedCount = shape.named == null ? 0 : shape.named.length;
+          result['length'] = positionalCount + namedCount;
+        }
+
         return result;
       }
     ''';
+
       final result = await inspector.jsCallFunctionOn(
           remoteObject, evalExpression, [remoteObject],
           returnByValue: true);
@@ -86,9 +170,14 @@
         jsName: metadata['name'],
         libraryId: metadata['libraryId'],
         dartName: metadata['dartName'],
+        isFunction: metadata['isFunction'],
+        isRecord: metadata['isRecord'],
+        isNativeError: metadata['isNativeError'],
         length: metadata['length'],
       );
-    } on ChromeDebugException {
+    } on ChromeDebugException catch (e, s) {
+      _logger.fine(
+          'Could not create class metadata for ${remoteObject.json}', e, s);
       return null;
     }
   }
@@ -106,4 +195,16 @@
 
   /// True if this class refers to system Lists, which are treated specially.
   bool get isSystemList => jsName == 'JSArray';
+
+  bool get isSet => jsName == '_HashSet';
+
+  /// True if this class refers to a function type.
+  bool isFunction;
+
+  /// True if this class refers to a record type.
+  bool isRecord;
+
+  /// True is this class refers to a native JS type.
+  /// i.e. inherits from NativeError.
+  bool isNativeError;
 }
diff --git a/dwds/lib/src/debugging/metadata/function.dart b/dwds/lib/src/debugging/metadata/function.dart
index bc435a0..16916e3 100644
--- a/dwds/lib/src/debugging/metadata/function.dart
+++ b/dwds/lib/src/debugging/metadata/function.dart
@@ -1,13 +1,12 @@
 // 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';
+// BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/utilities/server.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../../loaders/strategy.dart';
-import '../../utilities/shared.dart';
-import '../remote_debugger.dart';
-
 /// Meta data for a remote Dart function in Chrome.
 class FunctionMetaData {
   final String name;
diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart
index b045cbf..677bde8 100644
--- a/dwds/lib/src/debugging/metadata/provider.dart
+++ b/dwds/lib/src/debugging/metadata/provider.dart
@@ -1,17 +1,15 @@
 // 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';
+// BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:convert';
 
 import 'package:async/async.dart';
+import 'package:dwds/src/debugging/metadata/module_metadata.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as p;
 
-import '../../readers/asset_reader.dart';
-import 'module_metadata.dart';
-
 /// A provider of metadata in which data is collected through DDC outputs.
 class MetadataProvider {
   final AssetReader _assetReader;
diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart
index fc51c64..3b1635e 100644
--- a/dwds/lib/src/debugging/modules.dart
+++ b/dwds/lib/src/debugging/modules.dart
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:async/async.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/utilities/dart_uri.dart';
 import 'package:logging/logging.dart';
 
-import '../loaders/strategy.dart';
-import '../utilities/dart_uri.dart';
-
 /// Tracks modules for the compiled application.
 class Modules {
   final _logger = Logger('Modules');
diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart
index ae64e5e..cacea72 100644
--- a/dwds/lib/src/debugging/skip_list.dart
+++ b/dwds/lib/src/debugging/skip_list.dart
@@ -2,7 +2,7 @@
 // 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 'location.dart';
+import 'package:dwds/src/debugging/location.dart';
 
 const maxValue = 2147483647;
 
@@ -18,10 +18,10 @@
   /// https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-stepInto
   ///
   /// Can return a cached value.
-  Future<List<Map<String, dynamic>>> compute(
+  List<Map<String, dynamic>> compute(
     String scriptId,
     Set<Location> locations,
-  ) async {
+  ) {
     if (_idToList.containsKey(scriptId)) return _idToList[scriptId]!;
 
     final sortedLocations = locations.toList()
diff --git a/dwds/lib/src/debugging/webkit_debugger.dart b/dwds/lib/src/debugging/webkit_debugger.dart
index 98b2dc5..18f83ba 100644
--- a/dwds/lib/src/debugging/webkit_debugger.dart
+++ b/dwds/lib/src/debugging/webkit_debugger.dart
@@ -2,10 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/debugging/remote_debugger.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import 'remote_debugger.dart';
-
 /// A remote debugger with a Webkit Inspection Protocol connection.
 class WebkitDebugger implements RemoteDebugger {
   final WipDebugger _wipDebugger;
diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart
index 4ead6d7..f825018 100644
--- a/dwds/lib/src/dwds_vm_client.dart
+++ b/dwds/lib/src/dwds_vm_client.dart
@@ -5,17 +5,16 @@
 import 'dart:async';
 import 'dart:convert';
 
+import 'package:dwds/src/events.dart';
+import 'package:dwds/src/services/chrome_debug_exception.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:dwds/src/services/debug_service.dart';
 import 'package:dwds/src/utilities/synchronized.dart';
 import 'package:logging/logging.dart';
 import 'package:uuid/uuid.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import 'events.dart';
-import 'services/chrome_proxy_service.dart';
-import 'services/chrome_debug_exception.dart';
-import 'services/debug_service.dart';
-
 final _logger = Logger('DwdsVmClient');
 
 // A client of the vm service that registers some custom extensions like
@@ -149,7 +148,7 @@
   }
 
   Future<Map<String, dynamic>> hotRestart(
-      ChromeProxyService chromeProxyService, VmService client) async {
+      ChromeProxyService chromeProxyService, VmService client) {
     return _hotRestartQueue.run(() => _hotRestart(chromeProxyService, client));
   }
 }
diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart
index d51e2a2..c44d12b 100644
--- a/dwds/lib/src/handlers/dev_handler.dart
+++ b/dwds/lib/src/handlers/dev_handler.dart
@@ -6,39 +6,38 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:dwds/data/build_result.dart';
+import 'package:dwds/data/connect_request.dart';
+import 'package:dwds/data/debug_event.dart';
+import 'package:dwds/data/devtools_request.dart';
+import 'package:dwds/data/error_response.dart';
+import 'package:dwds/data/isolate_events.dart';
+import 'package:dwds/data/register_event.dart';
+import 'package:dwds/data/serializers.dart';
+import 'package:dwds/src/connections/app_connection.dart';
+import 'package:dwds/src/connections/debug_connection.dart';
+import 'package:dwds/src/debugging/execution_context.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/debugging/webkit_debugger.dart';
+import 'package:dwds/src/dwds_vm_client.dart';
+import 'package:dwds/src/events.dart';
+import 'package:dwds/src/handlers/injector.dart';
+import 'package:dwds/src/handlers/socket_connections.dart';
 import 'package:dwds/src/loaders/require.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/servers/devtools.dart';
+import 'package:dwds/src/servers/extension_backend.dart';
+import 'package:dwds/src/servers/extension_debugger.dart';
+import 'package:dwds/src/services/app_debug_services.dart';
+import 'package:dwds/src/services/debug_service.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
+import 'package:dwds/src/utilities/shared.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf.dart';
 import 'package:sse/server/sse_handler.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../../data/build_result.dart';
-import '../../data/connect_request.dart';
-import '../../data/debug_event.dart';
-import '../../data/devtools_request.dart';
-import '../../data/error_response.dart';
-import '../../data/isolate_events.dart';
-import '../../data/register_event.dart';
-import '../../data/serializers.dart';
-
-import '../connections/app_connection.dart';
-import '../connections/debug_connection.dart';
-import '../debugging/execution_context.dart';
-import '../debugging/remote_debugger.dart';
-import '../debugging/webkit_debugger.dart';
-import '../dwds_vm_client.dart';
-import '../events.dart';
-import '../handlers/socket_connections.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../servers/devtools.dart';
-import '../servers/extension_backend.dart';
-import '../services/app_debug_services.dart';
-import '../services/debug_service.dart';
-import '../services/expression_compiler.dart';
-import '../utilities/sdk_configuration.dart';
-import 'injector.dart';
-
 /// When enabled, this logs VM service protocol and Chrome debug protocol
 /// traffic to disk.
 ///
@@ -72,7 +71,6 @@
   final bool _launchDevToolsInNewWindow;
   final ExpressionCompiler? _expressionCompiler;
   final DwdsInjector _injected;
-  final SdkConfigurationProvider _sdkConfigurationProvider;
 
   /// Null until [close] is called.
   ///
@@ -96,7 +94,6 @@
     this._injected,
     this._spawnDds,
     this._launchDevToolsInNewWindow,
-    this._sdkConfigurationProvider,
   ) {
     _subs.add(buildResults.listen(_emitBuildResults));
     _listen();
@@ -121,9 +118,8 @@
         for (var handler in _sseHandlers.values) {
           handler.shutdown();
         }
-        await Future.wait(_servicesByAppId.values.map((service) async {
-          await service.close();
-        }));
+        await Future.wait(
+            _servicesByAppId.values.map((service) => service.close()));
         _servicesByAppId.clear();
       }();
 
@@ -164,7 +160,7 @@
           .takeUntilGap(const Duration(milliseconds: 50));
       // We enqueue this work as we need to begin listening (`.hasNext`)
       // before events are received.
-      unawaited(Future.microtask(() => connection.runtime.enable()));
+      safeUnawaited(Future.microtask(connection.runtime.enable));
 
       await for (var contextId in contextIds) {
         final result = await connection.sendCommand('Runtime.evaluate', {
@@ -180,7 +176,7 @@
         }
       }
       if (appTab != null) break;
-      unawaited(connection.close());
+      safeUnawaited(connection.close());
     }
     if (appTab == null || tabConnection == null || executionContext == null) {
       throw AppConnectionException(
@@ -219,7 +215,6 @@
       useSse: false,
       expressionCompiler: _expressionCompiler,
       spawnDds: _spawnDds,
-      sdkConfigurationProvider: _sdkConfigurationProvider,
     );
   }
 
@@ -240,7 +235,7 @@
           await _chromeConnection(), appConnection);
       appServices = await _createAppDebugServices(
           appConnection.request.appId, debugService);
-      unawaited(appServices.chromeProxyService.remoteDebugger.onClose.first
+      safeUnawaited(appServices.chromeProxyService.remoteDebugger.onClose.first
           .whenComplete(() async {
         await appServices?.close();
         _servicesByAppId.remove(appConnection.request.appId);
@@ -274,19 +269,19 @@
           if (message is DevToolsRequest) {
             await _handleDebugRequest(connection, injectedConnection);
           } else if (message is IsolateExit) {
-            await _handleIsolateExit(connection);
+            _handleIsolateExit(connection);
           } else if (message is IsolateStart) {
             await _handleIsolateStart(connection, injectedConnection);
           } else if (message is BatchedDebugEvents) {
-            await _servicesByAppId[connection.request.appId]
+            _servicesByAppId[connection.request.appId]
                 ?.chromeProxyService
                 .parseBatchedDebugEvents(message);
           } else if (message is DebugEvent) {
-            await _servicesByAppId[connection.request.appId]
+            _servicesByAppId[connection.request.appId]
                 ?.chromeProxyService
                 .parseDebugEvent(message);
           } else if (message is RegisterEvent) {
-            await _servicesByAppId[connection.request.appId]
+            _servicesByAppId[connection.request.appId]
                 ?.chromeProxyService
                 .parseRegisterEvent(message);
           }
@@ -307,7 +302,7 @@
       }
     });
 
-    unawaited(injectedConnection.sink.done.then((_) async {
+    safeUnawaited(injectedConnection.sink.done.then((_) {
       _injectedConnections.remove(injectedConnection);
       final connection = appConnection;
       if (connection != null) {
@@ -424,7 +419,7 @@
     return connection;
   }
 
-  Future<void> _handleIsolateExit(AppConnection appConnection) async {
+  void _handleIsolateExit(AppConnection appConnection) {
     _servicesByAppId[appConnection.request.appId]
         ?.chromeProxyService
         .destroyIsolate();
@@ -437,7 +432,7 @@
         .createIsolate(appConnection);
   }
 
-  void _listen() async {
+  void _listen() {
     _subs.add(_injected.devHandlerPaths.listen((devHandlerPath) async {
       final uri = Uri.parse(devHandlerPath);
       if (!_sseHandlers.containsKey(uri.path)) {
@@ -478,104 +473,126 @@
     return appDebugService;
   }
 
-  void _listenForDebugExtension() async {
-    while (await _extensionBackend!.connections.hasNext) {
-      _startExtensionDebugService();
+  Future<void> _listenForDebugExtension() async {
+    final extensionBackend = _extensionBackend;
+    if (extensionBackend == null) {
+      _logger.severe('No debug extension backend. Debugging will not work.');
+      return;
+    }
+
+    while (await extensionBackend.connections.hasNext) {
+      final extensionDebugger = await extensionBackend.extensionDebugger;
+      await _startExtensionDebugService(extensionDebugger);
     }
   }
 
   /// Starts a [DebugService] for Dart Debug Extension.
-  void _startExtensionDebugService() async {
-    final extensionDebugger = await _extensionBackend!.extensionDebugger;
+  Future<void> _startExtensionDebugService(
+      ExtensionDebugger extensionDebugger) async {
     // Waits for a `DevToolsRequest` to be sent from the extension background
     // when the extension is clicked.
     extensionDebugger.devToolsRequestStream.listen((devToolsRequest) async {
-      // TODO(grouma) - Ideally we surface those warnings to the extension so
-      // that it can be displayed to the user through an alert.
-      final tabUrl = devToolsRequest.tabUrl;
-      final appId = devToolsRequest.appId;
-      if (tabUrl == null) {
-        _logger.warning('Failed to start extension debug service. '
-            'Missing tab url in DevTools request for app with id: $appId');
-        return;
-      }
-      final connection = _appConnectionByAppId[appId];
-      if (connection == null) {
-        _logger.warning('Failed to start extension debug service. '
-            'Not connected to an app with id: $appId');
-        return;
-      }
-      final executionContext = extensionDebugger.executionContext;
-      if (executionContext == null) {
-        _logger.warning('Failed to start extension debug service. '
-            'No execution context for app with id: $appId');
-        return;
-      }
-
-      final debuggerStart = DateTime.now();
-      var appServices = _servicesByAppId[appId];
-      if (appServices == null) {
-        final debugService = await DebugService.start(
-          _hostname,
-          extensionDebugger,
-          executionContext,
-          basePathForServerUri(tabUrl),
-          _assetReader,
-          _loadStrategy,
-          connection,
-          _urlEncoder,
-          onResponse: (response) {
-            if (response['error'] == null) return;
-            _logger
-                .finest('VmService proxy responded with an error:\n$response');
-          },
-          useSse: _useSseForDebugProxy,
-          expressionCompiler: _expressionCompiler,
-          spawnDds: _spawnDds,
-          sdkConfigurationProvider: _sdkConfigurationProvider,
-        );
-        appServices = await _createAppDebugServices(
-          devToolsRequest.appId,
-          debugService,
-        );
-        final encodedUri = await debugService.encodedUri;
-        extensionDebugger.sendEvent('dwds.encodedUri', encodedUri);
-        unawaited(appServices.chromeProxyService.remoteDebugger.onClose.first
-            .whenComplete(() async {
-          appServices?.chromeProxyService.destroyIsolate();
-          await appServices?.close();
-          _servicesByAppId.remove(devToolsRequest.appId);
-          _logger.info('Stopped debug service on '
-              '${await appServices?.debugService.encodedUri}\n');
-        }));
-        extensionDebugConnections.add(DebugConnection(appServices));
-        _servicesByAppId[appId] = appServices;
-      }
-      final encodedUri = await appServices.debugService.encodedUri;
-
-      appServices.dwdsStats.updateLoadTime(
-          debuggerStart: debuggerStart, devToolsStart: DateTime.now());
-
-      if (_devTools != null) {
-        // If we only want the URI, this means we are embedding Dart DevTools in
-        // Chrome DevTools. Therefore return early.
-        if (devToolsRequest.uriOnly ?? false) {
-          final devToolsUri = _constructDevToolsUri(
-            encodedUri,
-            ideQueryParam: 'ChromeDevTools',
-          );
-          extensionDebugger.sendEvent('dwds.devtoolsUri', devToolsUri);
-          return;
-        }
-        final devToolsUri = _constructDevToolsUri(
-          encodedUri,
-          ideQueryParam: 'DebugExtension',
-        );
-        await _launchDevTools(extensionDebugger, devToolsUri);
+      try {
+        await _handleDevToolsRequest(extensionDebugger, devToolsRequest);
+      } catch (error) {
+        _logger.severe('Encountered error handling DevTools request.');
+        extensionDebugger.closeWithError(error);
       }
     });
   }
 
+  Future<void> _handleDevToolsRequest(
+    ExtensionDebugger extensionDebugger,
+    DevToolsRequest devToolsRequest,
+  ) async {
+    // TODO(grouma) - Ideally we surface those warnings to the extension so
+    // that it can be displayed to the user through an alert.
+    final tabUrl = devToolsRequest.tabUrl;
+    final appId = devToolsRequest.appId;
+    if (tabUrl == null) {
+      throw StateError('Failed to start extension debug service. '
+          'Missing tab url in DevTools request for app with id: $appId');
+    }
+    final connection = _appConnectionByAppId[appId];
+    if (connection == null) {
+      throw StateError('Failed to start extension debug service. '
+          'Not connected to an app with id: $appId');
+    }
+    final executionContext = extensionDebugger.executionContext;
+    if (executionContext == null) {
+      throw StateError('Failed to start extension debug service. '
+          'No execution context for app with id: $appId');
+    }
+
+    final debuggerStart = DateTime.now();
+    var appServices = _servicesByAppId[appId];
+    if (appServices == null) {
+      final debugService = await DebugService.start(
+        _hostname,
+        extensionDebugger,
+        executionContext,
+        basePathForServerUri(tabUrl),
+        _assetReader,
+        _loadStrategy,
+        connection,
+        _urlEncoder,
+        onResponse: (response) {
+          if (response['error'] == null) return;
+          _logger.finest('VmService proxy responded with an error:\n$response');
+        },
+        useSse: _useSseForDebugProxy,
+        expressionCompiler: _expressionCompiler,
+        spawnDds: _spawnDds,
+      );
+      appServices = await _createAppDebugServices(
+        devToolsRequest.appId,
+        debugService,
+      );
+      final encodedUri = await debugService.encodedUri;
+      extensionDebugger.sendEvent('dwds.encodedUri', encodedUri);
+      safeUnawaited(appServices.chromeProxyService.remoteDebugger.onClose.first
+          .whenComplete(() async {
+        appServices?.chromeProxyService.destroyIsolate();
+        await appServices?.close();
+        _servicesByAppId.remove(devToolsRequest.appId);
+        _logger.info('Stopped debug service on '
+            '${await appServices?.debugService.encodedUri}\n');
+      }));
+      extensionDebugConnections.add(DebugConnection(appServices));
+      _servicesByAppId[appId] = appServices;
+    }
+    // If we don't have a DevTools instance, then are connecting to an IDE.
+    // Therefore return early instead of opening DevTools:
+    if (_devTools == null) return;
+
+    final encodedUri = await appServices.debugService.encodedUri;
+
+    appServices.dwdsStats.updateLoadTime(
+        debuggerStart: debuggerStart, devToolsStart: DateTime.now());
+
+    // TODO(elliette): Remove handling requests from the MV2 extension after
+    // MV3 release.
+    // If we only want the URI, this means the Dart Debug Extension should
+    // handle how to open it. Therefore return early before opening a new
+    // tab or window:
+    if (devToolsRequest.uriOnly ?? false) {
+      final devToolsUri = _constructDevToolsUri(
+        encodedUri,
+        ideQueryParam: 'ChromeDevTools',
+      );
+      return extensionDebugger.sendEvent('dwds.devtoolsUri', devToolsUri);
+    }
+
+    // Otherwise, launch DevTools in a new tab / window:
+    await _launchDevTools(
+      extensionDebugger,
+      _constructDevToolsUri(
+        encodedUri,
+        ideQueryParam: 'DebugExtension',
+      ),
+    );
+  }
+
   DevTools _ensureDevTools() {
     final devTools = _devTools;
     if (devTools == null) {
diff --git a/dwds/lib/src/handlers/injector.dart b/dwds/lib/src/handlers/injector.dart
index b486cc0..81a04ef 100644
--- a/dwds/lib/src/handlers/injector.dart
+++ b/dwds/lib/src/handlers/injector.dart
@@ -8,12 +8,11 @@
 import 'dart:isolate';
 
 import 'package:crypto/crypto.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/version.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf.dart';
 
-import '../loaders/strategy.dart';
-import '../version.dart';
-
 /// File extension that build_web_compilers will place the
 /// [entrypointExtensionMarker] in.
 const bootstrapJsExtension = '.bootstrap.js';
@@ -37,16 +36,16 @@
   final bool _useSseForInjectedClient;
   final bool _emitDebugEvents;
   final bool _isInternalBuild;
-  final bool _isFlutterApp;
+  final Future<bool> Function() _isFlutterApp;
 
   DwdsInjector(
     this._loadStrategy, {
+    required bool enableDevtoolsLaunch,
+    required bool useSseForInjectedClient,
+    required bool emitDebugEvents,
+    required bool isInternalBuild,
+    required Future<bool> Function() isFlutterApp,
     Future<String>? extensionUri,
-    bool enableDevtoolsLaunch = false,
-    bool useSseForInjectedClient = true,
-    bool emitDebugEvents = true,
-    bool isInternalBuild = false,
-    bool isFlutterApp = false,
   })  : _extensionUri = extensionUri,
         _enableDevtoolsLaunch = enableDevtoolsLaunch,
         _useSseForInjectedClient = useSseForInjectedClient,
@@ -118,7 +117,7 @@
                 _enableDevtoolsLaunch,
                 _emitDebugEvents,
                 _isInternalBuild,
-                _isFlutterApp,
+                await _isFlutterApp(),
               );
               body += await _loadStrategy.bootstrapFor(entrypoint);
               _logger.info('Injected debugging metadata for '
diff --git a/dwds/lib/src/loaders/build_runner_require.dart b/dwds/lib/src/loaders/build_runner_require.dart
index 81685c3..ca76215 100644
--- a/dwds/lib/src/loaders/build_runner_require.dart
+++ b/dwds/lib/src/loaders/build_runner_require.dart
@@ -5,16 +5,15 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:dwds/src/debugging/metadata/provider.dart';
+import 'package:dwds/src/loaders/require.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as p;
 import 'package:shelf/shelf.dart';
 
-import '../debugging/metadata/provider.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../services/expression_compiler.dart';
-import 'require.dart';
-
 /// Provides a [RequireStrategy] suitable for use with `package:build_runner`.
 class BuildRunnerRequireStrategyProvider {
   final _logger = Logger('BuildRunnerRequireStrategyProvider');
diff --git a/dwds/lib/src/loaders/frontend_server_require.dart b/dwds/lib/src/loaders/frontend_server_require.dart
index 1b3dce2..f1459f2 100644
--- a/dwds/lib/src/loaders/frontend_server_require.dart
+++ b/dwds/lib/src/loaders/frontend_server_require.dart
@@ -2,14 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:dwds/src/debugging/metadata/provider.dart';
+import 'package:dwds/src/loaders/require.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:path/path.dart' as p;
 
-import '../debugging/metadata/provider.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../services/expression_compiler.dart';
-import 'require.dart';
-
 /// Provides a [RequireStrategy] suitable for use with Frontend Server.
 class FrontendServerRequireStrategyProvider {
   final ReloadConfiguration _configuration;
diff --git a/dwds/lib/src/loaders/legacy.dart b/dwds/lib/src/loaders/legacy.dart
index 2af1485..aa6c27a 100644
--- a/dwds/lib/src/loaders/legacy.dart
+++ b/dwds/lib/src/loaders/legacy.dart
@@ -2,13 +2,12 @@
 // 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:dwds/src/debugging/metadata/provider.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:shelf/shelf.dart';
 
-import '../debugging/metadata/provider.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../services/expression_compiler.dart';
-
 /// A load strategy for the legacy module system.
 class LegacyStrategy extends LoadStrategy {
   @override
@@ -101,8 +100,7 @@
       'window.\$dartLoader.forceLoadModule("$clientScript");\n';
 
   @override
-  Future<String?> moduleForServerPath(
-          String entrypoint, String serverPath) async =>
+  Future<String?> moduleForServerPath(String entrypoint, String serverPath) =>
       _moduleForServerPath(metadataProviderFor(entrypoint), serverPath);
 
   @override
@@ -110,12 +108,11 @@
       _moduleInfoForProvider(metadataProviderFor(entrypoint));
 
   @override
-  Future<String?> serverPathForModule(String entrypoint, String module) async =>
+  Future<String?> serverPathForModule(String entrypoint, String module) =>
       _serverPathForModule(metadataProviderFor(entrypoint), module);
 
   @override
-  Future<String?> sourceMapPathForModule(
-          String entrypoint, String module) async =>
+  Future<String?> sourceMapPathForModule(String entrypoint, String module) =>
       _sourceMapPathForModule(metadataProviderFor(entrypoint), module);
 
   @override
diff --git a/dwds/lib/src/loaders/require.dart b/dwds/lib/src/loaders/require.dart
index ce01bd1..b18b892 100644
--- a/dwds/lib/src/loaders/require.dart
+++ b/dwds/lib/src/loaders/require.dart
@@ -4,14 +4,13 @@
 
 import 'dart:convert';
 
+import 'package:dwds/src/debugging/metadata/provider.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:path/path.dart' as p;
 import 'package:shelf/shelf.dart';
 
-import '../debugging/metadata/provider.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../services/expression_compiler.dart';
-
 /// Find the path we are serving from the url.
 ///
 /// Example:
diff --git a/dwds/lib/src/loaders/strategy.dart b/dwds/lib/src/loaders/strategy.dart
index d019ac1..ab6cd1a 100644
--- a/dwds/lib/src/loaders/strategy.dart
+++ b/dwds/lib/src/loaders/strategy.dart
@@ -2,12 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/debugging/metadata/provider.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:shelf/shelf.dart';
 
-import '../debugging/metadata/provider.dart';
-import '../readers/asset_reader.dart';
-import '../services/expression_compiler.dart';
-
 late LoadStrategy _globalLoadStrategy;
 
 set globalLoadStrategy(LoadStrategy strategy) => _globalLoadStrategy = strategy;
diff --git a/dwds/lib/src/readers/frontend_server_asset_reader.dart b/dwds/lib/src/readers/frontend_server_asset_reader.dart
index 4fc8309..ecdc7ca 100644
--- a/dwds/lib/src/readers/frontend_server_asset_reader.dart
+++ b/dwds/lib/src/readers/frontend_server_asset_reader.dart
@@ -5,12 +5,11 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:dwds/src/readers/asset_reader.dart';
 import 'package:logging/logging.dart';
 import 'package:package_config/package_config.dart';
 import 'package:path/path.dart' as p;
 
-import '../readers/asset_reader.dart';
-
 /// A reader for Dart sources and related source maps provided by the Frontend
 /// Server.
 class FrontendServerAssetReader implements AssetReader {
@@ -86,22 +85,22 @@
   /// Updates the internal caches by reading the Frontend Server output files.
   ///
   /// Will only read the incremental files on additional calls.
-  Future<void> updateCaches() async {
+  void updateCaches() {
     if (!_haveReadOriginals) {
-      await _updateCaches(_mapOriginal, _jsonOriginal);
+      _updateCaches(_mapOriginal, _jsonOriginal);
       _haveReadOriginals = true;
     } else {
-      await _updateCaches(_mapIncremental, _jsonIncremental);
+      _updateCaches(_mapIncremental, _jsonIncremental);
     }
   }
 
-  Future<void> _updateCaches(File map, File json) async {
-    if (!(await map.exists() && await json.exists())) {
+  void _updateCaches(File map, File json) {
+    if (!(map.existsSync() && json.existsSync())) {
       throw StateError('$map and $json do not exist.');
     }
-    final sourceContents = await map.readAsBytes();
+    final sourceContents = map.readAsBytesSync();
     final sourceInfo =
-        jsonDecode(await json.readAsString()) as Map<String, dynamic>;
+        jsonDecode(json.readAsStringSync()) as Map<String, dynamic>;
     for (var key in sourceInfo.keys) {
       final info = sourceInfo[key];
       _mapContents[key] = utf8.decode(sourceContents
diff --git a/dwds/lib/src/readers/proxy_server_asset_reader.dart b/dwds/lib/src/readers/proxy_server_asset_reader.dart
index 960809e..18edda6 100644
--- a/dwds/lib/src/readers/proxy_server_asset_reader.dart
+++ b/dwds/lib/src/readers/proxy_server_asset_reader.dart
@@ -2,18 +2,16 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:dwds/src/readers/asset_reader.dart';
 import 'package:http/http.dart' as http;
 import 'package:http/io_client.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf.dart';
 import 'package:shelf_proxy/shelf_proxy.dart';
 
-import 'asset_reader.dart';
-
 /// A reader for resources provided by a proxy server.
 class ProxyServerAssetReader implements AssetReader {
   final _logger = Logger('ProxyServerAssetReader');
diff --git a/dwds/lib/src/servers/extension_backend.dart b/dwds/lib/src/servers/extension_backend.dart
index cf22f87..772b6aa 100644
--- a/dwds/lib/src/servers/extension_backend.dart
+++ b/dwds/lib/src/servers/extension_backend.dart
@@ -2,19 +2,17 @@
 // 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 'package:async/async.dart';
+import 'package:dwds/data/extension_request.dart';
+import 'package:dwds/src/events.dart';
+import 'package:dwds/src/handlers/socket_connections.dart';
+import 'package:dwds/src/servers/extension_debugger.dart';
+import 'package:dwds/src/utilities/server.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf.dart';
 
-import '../../data/extension_request.dart';
-import '../events.dart';
-import '../handlers/socket_connections.dart';
-import '../utilities/shared.dart';
-import 'extension_debugger.dart';
-
 const authenticationResponse = 'Dart Debug Authentication Success!\n\n'
     'You can close this tab and launch the Dart Debug Extension again.';
 
diff --git a/dwds/lib/src/servers/extension_debugger.dart b/dwds/lib/src/servers/extension_debugger.dart
index 856695e..66247d7 100644
--- a/dwds/lib/src/servers/extension_debugger.dart
+++ b/dwds/lib/src/servers/extension_debugger.dart
@@ -6,15 +6,18 @@
 import 'dart:collection';
 import 'dart:convert';
 
-import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+import 'package:dwds/data/devtools_request.dart';
+import 'package:dwds/data/extension_request.dart';
+import 'package:dwds/data/serializers.dart';
+import 'package:dwds/src/debugging/execution_context.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/handlers/socket_connections.dart';
+import 'package:dwds/src/services/chrome_debug_exception.dart';
+import 'package:logging/logging.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
+    hide StackTrace;
 
-import '../../data/devtools_request.dart';
-import '../../data/extension_request.dart';
-import '../../data/serializers.dart';
-import '../debugging/execution_context.dart';
-import '../debugging/remote_debugger.dart';
-import '../handlers/socket_connections.dart';
-import '../services/chrome_debug_exception.dart';
+final _logger = Logger('ExtensionDebugger');
 
 /// A remote debugger backed by the Dart Debug Extension with an SSE connection.
 class ExtensionDebugger implements RemoteDebugger {
@@ -141,11 +144,23 @@
     final completer = Completer<WipResponse>();
     final id = newId();
     _completers[id] = completer;
-    sseConnection.sink
-        .add(jsonEncode(serializers.serialize(ExtensionRequest((b) => b
-          ..id = id
-          ..command = command
-          ..commandParams = jsonEncode(params ?? {})))));
+    try {
+      sseConnection.sink
+          .add(jsonEncode(serializers.serialize(ExtensionRequest((b) => b
+            ..id = id
+            ..command = command
+            ..commandParams = jsonEncode(params ?? {})))));
+    } on StateError catch (error, stackTrace) {
+      if (error.message.contains('Cannot add event after closing')) {
+        _logger.severe('Socket connection closed. Shutting down debugger.');
+        closeWithError(error);
+      } else {
+        _logger.severe('Bad state while sending $command.', error, stackTrace);
+      }
+    } catch (error, stackTrace) {
+      _logger.severe(
+          'Unknown error while sending $command.', error, stackTrace);
+    }
     return completer.future;
   }
 
@@ -162,6 +177,13 @@
         ]);
       }();
 
+  void closeWithError(Object? error) {
+    _logger.shout(
+        'Closing extension debugger due to error. Restart app for debugging functionality',
+        error);
+    close();
+  }
+
   @override
   Future disable() => sendCommand('Debugger.disable');
 
diff --git a/dwds/lib/src/services/app_debug_services.dart b/dwds/lib/src/services/app_debug_services.dart
index 1f793e3..2e07a44 100644
--- a/dwds/lib/src/services/app_debug_services.dart
+++ b/dwds/lib/src/services/app_debug_services.dart
@@ -2,12 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
-
-import '../dwds_vm_client.dart';
-import '../events.dart';
-import 'chrome_proxy_service.dart' show ChromeProxyService;
-import 'debug_service.dart';
+import 'package:dwds/src/dwds_vm_client.dart';
+import 'package:dwds/src/events.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart'
+    show ChromeProxyService;
+import 'package:dwds/src/services/debug_service.dart';
 
 /// A container for all the services required for debugging an application.
 class AppDebugServices {
diff --git a/dwds/lib/src/services/batched_expression_evaluator.dart b/dwds/lib/src/services/batched_expression_evaluator.dart
index 81bf7f0..6881dcc 100644
--- a/dwds/lib/src/services/batched_expression_evaluator.dart
+++ b/dwds/lib/src/services/batched_expression_evaluator.dart
@@ -5,17 +5,17 @@
 import 'dart:async';
 
 import 'package:collection/collection.dart';
+import 'package:dwds/shared/batched_stream.dart';
+import 'package:dwds/src/debugging/debugger.dart';
+import 'package:dwds/src/debugging/location.dart';
+import 'package:dwds/src/debugging/modules.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
+import 'package:dwds/src/services/expression_evaluator.dart';
 import 'package:dwds/src/utilities/domain.dart';
+import 'package:dwds/src/utilities/shared.dart';
 import 'package:logging/logging.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../debugging/debugger.dart';
-import '../debugging/location.dart';
-import '../debugging/modules.dart';
-import '../utilities/batched_stream.dart';
-import 'expression_compiler.dart';
-import 'expression_evaluator.dart';
-
 class EvaluateRequest {
   final String isolateId;
   final String? libraryUri;
@@ -68,7 +68,7 @@
     return request.completer.future;
   }
 
-  void _processRequest(List<EvaluateRequest> requests) async {
+  void _processRequest(List<EvaluateRequest> requests) {
     String? libraryUri;
     String? isolateId;
     Map<String, String>? scope;
@@ -93,7 +93,7 @@
           _logger.fine(' - scope: $scope != ${request.scope}');
         }
 
-        unawaited(_evaluateBatch(currentRequests));
+        safeUnawaited(_evaluateBatch(currentRequests));
         currentRequests = [];
         libraryUri = request.libraryUri;
         isolateId = request.isolateId;
@@ -101,7 +101,7 @@
       }
       currentRequests.add(request);
     }
-    unawaited(_evaluateBatch(currentRequests));
+    safeUnawaited(_evaluateBatch(currentRequests));
   }
 
   Future<void> _evaluateBatch(List<EvaluateRequest> requests) async {
@@ -135,14 +135,19 @@
             createError(ErrorKind.internal, 'No batch result object ID.');
         request.completer.complete(error);
       } else {
-        unawaited(_debugger
-            .getProperties(listId, offset: i, count: 1, length: requests.length)
-            .then((v) {
-          final result = v.first.value;
-          _logger.fine(
-              'Got result out of a batch for ${request.expression}: $result');
-          request.completer.complete(result);
-        }));
+        safeUnawaited(
+          _debugger
+              .getProperties(listId,
+                  offset: i, count: 1, length: requests.length)
+              .then((v) {
+            final result = v.first.value;
+            _logger.fine(
+                'Got result out of a batch for ${request.expression}: $result');
+            request.completer.complete(result);
+          }),
+          onError: (error, stackTrace) =>
+              request.completer.completeError(error, stackTrace),
+        );
       }
     }
   }
diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart
index 766f3e9..4b0dec9 100644
--- a/dwds/lib/src/services/chrome_proxy_service.dart
+++ b/dwds/lib/src/services/chrome_proxy_service.dart
@@ -6,32 +6,30 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:dwds/data/debug_event.dart';
+import 'package:dwds/data/register_event.dart';
+import 'package:dwds/src/connections/app_connection.dart';
+import 'package:dwds/src/debugging/debugger.dart';
+import 'package:dwds/src/debugging/execution_context.dart';
+import 'package:dwds/src/debugging/inspector.dart';
+import 'package:dwds/src/debugging/instance.dart';
+import 'package:dwds/src/debugging/location.dart';
+import 'package:dwds/src/debugging/modules.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/debugging/skip_list.dart';
+import 'package:dwds/src/events.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/services/batched_expression_evaluator.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
+import 'package:dwds/src/services/expression_evaluator.dart';
+import 'package:dwds/src/utilities/dart_uri.dart';
+import 'package:dwds/src/utilities/shared.dart';
 import 'package:logging/logging.dart' hide LogRecord;
 import 'package:pub_semver/pub_semver.dart' as semver;
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../../data/debug_event.dart';
-import '../../data/register_event.dart';
-import '../connections/app_connection.dart';
-import '../debugging/debugger.dart';
-import '../debugging/execution_context.dart';
-import '../debugging/inspector.dart';
-import '../debugging/instance.dart';
-import '../debugging/location.dart';
-import '../debugging/modules.dart';
-import '../debugging/remote_debugger.dart';
-import '../debugging/skip_list.dart';
-import '../events.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../services/expression_compiler.dart';
-import '../utilities/dart_uri.dart';
-import '../utilities/sdk_configuration.dart';
-import '../utilities/shared.dart';
-import 'expression_evaluator.dart';
-import 'batched_expression_evaluator.dart';
-
 /// A proxy from the chrome debug protocol to the dart vm service protocol.
 class ChromeProxyService implements VmServiceInterface {
   /// Cache of all existing StreamControllers.
@@ -100,8 +98,6 @@
   final ExpressionCompiler? _compiler;
   ExpressionEvaluator? _expressionEvaluator;
 
-  final SdkConfigurationProvider _sdkConfigurationProvider;
-
   bool terminatingIsolates = false;
 
   ChromeProxyService._(
@@ -114,7 +110,6 @@
     this._skipLists,
     this.executionContext,
     this._compiler,
-    this._sdkConfigurationProvider,
   ) {
     final debugger = Debugger.create(
       remoteDebugger,
@@ -123,7 +118,7 @@
       _skipLists,
       root,
     );
-    debugger.then((value) => _debuggerCompleter.complete(value));
+    debugger.then(_debuggerCompleter.complete);
   }
 
   static Future<ChromeProxyService> create(
@@ -134,7 +129,6 @@
     AppConnection appConnection,
     ExecutionContext executionContext,
     ExpressionCompiler? expressionCompiler,
-    SdkConfigurationProvider sdkConfigurationProvider,
   ) async {
     final vm = VM(
       name: 'ChromeDebugProxy',
@@ -164,9 +158,8 @@
       skipLists,
       executionContext,
       expressionCompiler,
-      sdkConfigurationProvider,
     );
-    unawaited(service.createIsolate(appConnection));
+    safeUnawaited(service.createIsolate(appConnection));
     return service;
   }
 
@@ -177,7 +170,7 @@
     _skipLists.initialize();
     // We do not need to wait for compiler dependencies to be updated as the
     // [ExpressionEvaluator] is robust to evaluation requests during updates.
-    unawaited(_updateCompilerDependencies(entrypoint));
+    safeUnawaited(_updateCompilerDependencies(entrypoint));
   }
 
   Future<void> _updateCompilerDependencies(String entrypoint) async {
@@ -252,7 +245,6 @@
     final debugger = await debuggerFuture;
     final entrypoint = appConnection.request.entrypointPath;
     await _initializeEntrypoint(entrypoint);
-    final sdkConfiguration = await _sdkConfigurationProvider.configuration;
 
     debugger.notifyPausedAtStart();
     _inspector = await AppInspector.create(
@@ -263,7 +255,6 @@
       root,
       debugger,
       executionContext,
-      sdkConfiguration,
     );
     debugger.updateInspector(inspector);
 
@@ -279,18 +270,18 @@
             compiler,
           );
 
-    unawaited(_prewarmExpressionCompilerCache());
+    safeUnawaited(_prewarmExpressionCompilerCache());
 
     await debugger.reestablishBreakpoints(
         _previousBreakpoints, _disabledBreakpoints);
     _disabledBreakpoints.clear();
 
-    unawaited(appConnection.onStart.then((_) async {
+    safeUnawaited(appConnection.onStart.then((_) async {
       await debugger.resumeFromStart();
       _startedCompleter.complete();
     }));
 
-    unawaited(appConnection.onDone.then((_) => destroyIsolate()));
+    safeUnawaited(appConnection.onDone.then((_) => destroyIsolate()));
 
     final isolateRef = inspector.isolateRef;
     final timestamp = DateTime.now().millisecondsSinceEpoch;
@@ -497,7 +488,7 @@
     String expression, {
     Map<String, String>? scope,
     bool? disableBreakpoints,
-  }) async {
+  }) {
     // TODO(798) - respect disableBreakpoints.
     return captureElapsedTime(() async {
       await isInitialized;
@@ -521,7 +512,7 @@
   @override
   Future<Response> evaluateInFrame(
       String isolateId, int frameIndex, String expression,
-      {Map<String, String>? scope, bool? disableBreakpoints}) async {
+      {Map<String, String>? scope, bool? disableBreakpoints}) {
     // TODO(798) - respect disableBreakpoints.
 
     return captureElapsedTime(() async {
@@ -572,7 +563,7 @@
   }
 
   @override
-  Future<Isolate> getIsolate(String isolateId) async {
+  Future<Isolate> getIsolate(String isolateId) {
     return captureElapsedTime(() async {
       await isInitialized;
       _checkIsolate('getIsolate', isolateId);
@@ -596,8 +587,8 @@
   }
 
   @override
-  Future<ScriptList> getScripts(String isolateId) async {
-    return await captureElapsedTime(() async {
+  Future<ScriptList> getScripts(String isolateId) {
+    return captureElapsedTime(() async {
       await isInitialized;
       _checkIsolate('getScripts', isolateId);
       return inspector.getScripts();
@@ -614,8 +605,8 @@
     bool? forceCompile,
     bool? reportLines,
     List<String>? libraryFilters,
-  }) async {
-    return await captureElapsedTime(() async {
+  }) {
+    return captureElapsedTime(() async {
       await isInitialized;
       _checkIsolate('getSourceReport', isolateId);
       return await inspector.getSourceReport(
@@ -644,7 +635,7 @@
   }
 
   @override
-  Future<VM> getVM() async {
+  Future<VM> getVM() {
     return captureElapsedTime(() async {
       await isInitialized;
       return _vm;
@@ -716,7 +707,7 @@
         default:
           throw RPCError(
             'streamListen',
-            RPCError.kMethodNotFound,
+            RPCError.kInvalidParams,
             'The stream `$streamId` is not supported on web devices',
           );
       }
@@ -748,7 +739,7 @@
   }
 
   @override
-  Future<Success> registerService(String service, String alias) async {
+  Future<Success> registerService(String service, String alias) {
     return _rpcNotSupportedFuture('registerService');
   }
 
@@ -926,15 +917,15 @@
 
   /// Parses the [BatchedDebugEvents] and emits corresponding Dart VM Service
   /// protocol [Event]s.
-  Future<void> parseBatchedDebugEvents(BatchedDebugEvents debugEvents) async {
+  void parseBatchedDebugEvents(BatchedDebugEvents debugEvents) {
     for (var debugEvent in debugEvents.events) {
-      await parseDebugEvent(debugEvent);
+      parseDebugEvent(debugEvent);
     }
   }
 
   /// Parses the [DebugEvent] and emits a corresponding Dart VM Service
   /// protocol [Event].
-  Future<void> parseDebugEvent(DebugEvent debugEvent) async {
+  void parseDebugEvent(DebugEvent debugEvent) {
     if (terminatingIsolates) return;
     if (!_isIsolateRunning) return;
     final isolateRef = inspector.isolateRef;
@@ -952,7 +943,7 @@
 
   /// Parses the [RegisterEvent] and emits a corresponding Dart VM Service
   /// protocol [Event].
-  Future<void> parseRegisterEvent(RegisterEvent registerEvent) async {
+  void parseRegisterEvent(RegisterEvent registerEvent) {
     if (terminatingIsolates) return;
     if (!_isIsolateRunning) return;
 
@@ -1000,7 +991,7 @@
                 ..timestamp = event.timestamp.toInt());
           break;
         case 'dart.developer.log':
-          _handleDeveloperLog(isolateRef, event);
+          await _handleDeveloperLog(isolateRef, event);
           break;
         default:
           break;
@@ -1014,7 +1005,8 @@
     controller.add(event);
   }
 
-  void _handleDeveloperLog(IsolateRef isolateRef, ConsoleAPIEvent event) async {
+  Future<void> _handleDeveloperLog(
+      IsolateRef isolateRef, ConsoleAPIEvent event) async {
     final logObject = event.params?['args'][1] as Map?;
     final logParams = <String, RemoteObject>{};
     for (dynamic obj in logObject?['preview']?['properties'] ?? {}) {
@@ -1131,7 +1123,7 @@
 
   @override
   Future<Success> streamCpuSamplesWithUserTag(List<String> userTags) =>
-      throw UnimplementedError();
+      _rpcNotSupportedFuture('streamCpuSamplesWithUserTag');
 
   /// Prevent DWDS from blocking Dart SDK rolls if changes in package:vm_service
   /// are unimplemented in DWDS.
diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart
index deb3686..a4d6818 100644
--- a/dwds/lib/src/services/debug_service.dart
+++ b/dwds/lib/src/services/debug_service.dart
@@ -9,6 +9,16 @@
 import 'dart:typed_data';
 
 import 'package:dds/dds.dart';
+import 'package:dwds/src/connections/app_connection.dart';
+import 'package:dwds/src/debugging/execution_context.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
+import 'package:dwds/src/events.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/readers/asset_reader.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
+import 'package:dwds/src/utilities/server.dart';
+import 'package:dwds/src/utilities/shared.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:shelf/shelf.dart' hide Response;
@@ -17,17 +27,6 @@
 import 'package:vm_service/vm_service.dart';
 import 'package:web_socket_channel/web_socket_channel.dart';
 
-import '../connections/app_connection.dart';
-import '../loaders/strategy.dart';
-import '../readers/asset_reader.dart';
-import '../services/expression_compiler.dart';
-import '../debugging/execution_context.dart';
-import '../debugging/remote_debugger.dart';
-import '../events.dart';
-import '../utilities/shared.dart';
-import '../utilities/sdk_configuration.dart';
-import 'chrome_proxy_service.dart';
-
 bool _acceptNewConnections = true;
 int _clientsConnected = 0;
 
@@ -61,7 +60,7 @@
     VmServerConnection(inputStream, responseController.sink,
             serviceExtensionRegistry, chromeProxyService)
         .done
-        .whenComplete(() async {
+        .whenComplete(() {
       --_clientsConnected;
       if (!_acceptNewConnections && _clientsConnected == 0) {
         // DDS has disconnected so we can allow for clients to connect directly
@@ -86,7 +85,8 @@
       if (onResponse != null) onResponse(response);
       return jsonEncode(response);
     }).listen(connection.sink.add);
-    unawaited(chromeProxyService.remoteDebugger.onClose.first.whenComplete(() {
+    safeUnawaited(
+        chromeProxyService.remoteDebugger.onClose.first.whenComplete(() {
       connection.sink.close();
       sub.cancel();
     }));
@@ -98,7 +98,7 @@
     ++_clientsConnected;
     final vmServerConnection = VmServerConnection(inputStream,
         responseController.sink, serviceExtensionRegistry, chromeProxyService);
-    unawaited(vmServerConnection.done.whenComplete(() {
+    safeUnawaited(vmServerConnection.done.whenComplete(() {
       --_clientsConnected;
       if (!_acceptNewConnections && _clientsConnected == 0) {
         // DDS has disconnected so we can allow for clients to connect directly
@@ -219,7 +219,6 @@
     bool spawnDds = true,
     bool useSse = false,
     ExpressionCompiler? expressionCompiler,
-    required SdkConfigurationProvider sdkConfigurationProvider,
   }) async {
     final chromeProxyService = await ChromeProxyService.create(
       remoteDebugger,
@@ -229,7 +228,6 @@
       appConnection,
       executionContext,
       expressionCompiler,
-      sdkConfigurationProvider,
     );
     final authToken = _makeAuthToken();
     final serviceExtensionRegistry = ServiceExtensionRegistry();
@@ -239,7 +237,7 @@
       final sseHandler = SseHandler(Uri.parse('/$authToken/\$debugHandler'),
           keepAlive: const Duration(seconds: 5));
       handler = sseHandler.handler;
-      unawaited(_handleSseConnections(
+      safeUnawaited(_handleSseConnections(
           sseHandler, chromeProxyService, serviceExtensionRegistry,
           onRequest: onRequest, onResponse: onResponse));
     } else {
diff --git a/dwds/lib/src/services/expression_compiler_service.dart b/dwds/lib/src/services/expression_compiler_service.dart
index e94be84..aac6e63 100644
--- a/dwds/lib/src/services/expression_compiler_service.dart
+++ b/dwds/lib/src/services/expression_compiler_service.dart
@@ -6,11 +6,10 @@
 import 'dart:isolate';
 
 import 'package:async/async.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
+import 'package:dwds/src/utilities/sdk_configuration.dart';
 import 'package:logging/logging.dart';
 
-import '../utilities/sdk_configuration.dart';
-import 'expression_compiler.dart';
-
 class _Compiler {
   static final _logger = Logger('ExpressionCompilerService');
   final StreamQueue<dynamic> _responseQueue;
@@ -78,7 +77,6 @@
       sdkConfiguration.validateWeakSummaries();
     }
 
-    final librariesUri = sdkConfiguration.librariesUri!;
     final workerUri = sdkConfiguration.compilerWorkerUri!;
     final sdkSummaryUri = soundNullSafety
         ? sdkConfiguration.soundSdkSummaryUri!
@@ -86,8 +84,6 @@
 
     final args = [
       '--experimental-expression-compiler',
-      '--libraries-file',
-      '$librariesUri',
       '--dart-sdk-summary',
       '$sdkSummaryUri',
       '--asset-server-address',
@@ -98,7 +94,7 @@
       moduleFormat,
       if (verbose) '--verbose',
       soundNullSafety ? '--sound-null-safety' : '--no-sound-null-safety',
-      for (var experiment in experiments) '--enable-experiment=$experiment',
+      for (final experiment in experiments) '--enable-experiment=$experiment',
     ];
 
     _logger.info('Starting...');
@@ -211,7 +207,7 @@
   ///
   /// Terminates the isolate running expression compiler worker
   /// and marks the service as stopped.
-  Future<void> stop() async {
+  void stop() {
     _sendPort.send({'command': 'Shutdown'});
     _receivePort.close();
     _logger.info('Stopped.');
@@ -240,17 +236,15 @@
   final List<String> experiments;
   final bool _verbose;
 
-  final SdkConfigurationProvider _sdkConfigurationProvider;
+  final SdkConfigurationProvider sdkConfigurationProvider;
 
   ExpressionCompilerService(
     this._address,
     this._port, {
     bool verbose = false,
-    SdkConfigurationProvider sdkConfigurationProvider =
-        const DefaultSdkConfigurationProvider(),
+    required this.sdkConfigurationProvider,
     this.experiments = const [],
-  })  : _verbose = verbose,
-        _sdkConfigurationProvider = sdkConfigurationProvider;
+  }) : _verbose = verbose;
 
   @override
   Future<ExpressionCompilationResult> compileExpressionToJs(
@@ -275,7 +269,7 @@
       await _port,
       moduleFormat,
       soundNullSafety,
-      await _sdkConfigurationProvider.configuration,
+      await sdkConfigurationProvider.configuration,
       experiments,
       _verbose,
     );
diff --git a/dwds/lib/src/services/expression_evaluator.dart b/dwds/lib/src/services/expression_evaluator.dart
index e932775..7344181 100644
--- a/dwds/lib/src/services/expression_evaluator.dart
+++ b/dwds/lib/src/services/expression_evaluator.dart
@@ -2,18 +2,17 @@
 // 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:dwds/src/debugging/dart_scope.dart';
+import 'package:dwds/src/debugging/debugger.dart';
+import 'package:dwds/src/debugging/location.dart';
+import 'package:dwds/src/debugging/modules.dart';
+import 'package:dwds/src/loaders/strategy.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:dwds/src/utilities/domain.dart';
+import 'package:dwds/src/utilities/objects.dart' as chrome;
 import 'package:logging/logging.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../debugging/dart_scope.dart';
-import '../debugging/debugger.dart';
-import '../debugging/location.dart';
-import '../debugging/modules.dart';
-import '../loaders/strategy.dart';
-import '../utilities/objects.dart' as chrome;
-import 'expression_compiler.dart';
-
 class ErrorKind {
   const ErrorKind._(this._kind);
 
@@ -144,7 +143,7 @@
   /// [expression] dart expression to evaluate.
   Future<RemoteObject> evaluateExpressionInFrame(String isolateId,
       int frameIndex, String expression, Map<String, String>? scope) async {
-    if (scope != null) {
+    if (scope != null && scope.isNotEmpty) {
       // TODO(annagrin): Implement scope support.
       // Issue: https://github.com/dart-lang/webdev/issues/1344
       return createError(
@@ -208,6 +207,10 @@
 
     // Compile expression using an expression compiler, such as
     // frontend server or expression compiler worker.
+    //
+    // TODO(annagrin): map JS locals to dart locals in the expression
+    // and JS scope before passing them to the dart expression compiler.
+    // Issue:  https://github.com/dart-lang/sdk/issues/40273
     final compilationResult = await _compiler.compileExpressionToJs(
         isolateId,
         libraryUri.toString(),
diff --git a/dwds/lib/src/utilities/dart_uri.dart b/dwds/lib/src/utilities/dart_uri.dart
index 1049d96..88e81dc 100644
--- a/dwds/lib/src/utilities/dart_uri.dart
+++ b/dwds/lib/src/utilities/dart_uri.dart
@@ -2,13 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/loaders/strategy.dart';
 import 'package:logging/logging.dart';
 import 'package:package_config/package_config.dart';
 import 'package:path/path.dart' as p;
 
-import '../loaders/strategy.dart';
-import 'sdk_configuration.dart';
-
 /// The URI for a particular Dart file, able to canonicalize from various
 /// different representations.
 class DartUri {
@@ -152,17 +150,11 @@
   static String currentDirectoryUri = '${p.toUri(currentDirectory)}';
 
   /// Record library and script uris to enable resolving library and script paths.
-  static Future<void> initialize(SdkConfiguration sdkConfiguration) async {
+  static Future<void> initialize() async {
     final packagesUri =
         p.toUri(p.join(currentDirectory, '.dart_tool/package_config.json'));
 
     clear();
-
-    // Allow for tests to supply empty configurations.
-    if (sdkConfiguration.sdkDirectory != null) {
-      sdkConfiguration.validateSdkDir();
-    }
-
     await _loadPackageConfig(packagesUri);
   }
 
@@ -174,7 +166,7 @@
   }
 
   /// Record all of the libraries, indexed by their absolute file: URI.
-  static Future<void> recordAbsoluteUris(Iterable<String> libraryUris) async {
+  static void recordAbsoluteUris(Iterable<String> libraryUris) {
     for (var uri in libraryUris) {
       _recordAbsoluteUri(uri);
     }
diff --git a/dwds/lib/src/utilities/domain.dart b/dwds/lib/src/utilities/domain.dart
index a41e721..6fedc5d 100644
--- a/dwds/lib/src/utilities/domain.dart
+++ b/dwds/lib/src/utilities/domain.dart
@@ -1,13 +1,12 @@
 // Copyright (c) 2019, 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';
+// BSD-style license that can be found in the LICENSE file.
 
+import 'package:dwds/src/connections/app_connection.dart';
+import 'package:dwds/src/debugging/remote_debugger.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-import '../connections/app_connection.dart';
-import '../debugging/remote_debugger.dart';
-
 abstract class AppInspectorInterface {
   /// Connection to the app running in the browser.
   AppConnection get appConnection;
diff --git a/dwds/lib/src/utilities/objects.dart b/dwds/lib/src/utilities/objects.dart
index bf13fd5..4b3444e 100644
--- a/dwds/lib/src/utilities/objects.dart
+++ b/dwds/lib/src/utilities/objects.dart
@@ -1,6 +1,6 @@
 // Copyright (c) 2019, 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';
+// BSD-style license that can be found in the LICENSE file.
 
 /// A library for WebKit mirror objects and support code. These probably should
 /// get migrated into webkit_inspection_protocol over time.
diff --git a/dwds/lib/src/utilities/sdk_configuration.dart b/dwds/lib/src/utilities/sdk_configuration.dart
index 4e20b73..a1198eb 100644
--- a/dwds/lib/src/utilities/sdk_configuration.dart
+++ b/dwds/lib/src/utilities/sdk_configuration.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:io';
 
 import 'package:file/file.dart';
@@ -32,154 +31,80 @@
   Future<SdkConfiguration> get configuration;
 }
 
-/// Sdk layout.
+/// Dart SDK layout.
 ///
 /// Contains definition of the default SDK layout.
 /// We keep all the path constants in one place for ease of update.
 class SdkLayout {
-  static final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
-  static final defaultSdkLayout = createDefault(sdkDir);
-
-  static SdkLayout createDefault(String sdkDirectory) {
-    final sdkJsWeakFileName = 'dart_sdk.js';
-    final sdkJsMapWeakFileName = 'dart_sdk.js.map';
-    final sdkJsSoundFileName = 'dart_sdk_sound.js';
-    final sdkJsMapSoundFileName = 'dart_sdk_sound.js.map';
-    final sdkSummarySoundFileName = 'ddc_outline.dill';
-    final sdkSummaryWeakFileName = 'ddc_outline_unsound.dill';
-    final sdkFullDillSoundFileName = 'ddc_platform.dill';
-    final sdkFullDillWeakFileName = 'ddc_platform_unsound.dill';
-
-    final sdkSummaryDirectory = p.join(sdkDirectory, 'lib', '_internal');
-    final sdkJsDirectory =
-        p.join(sdkDirectory, 'lib', 'dev_compiler', 'kernel', 'amd');
-
-    final soundSummaryPath =
-        p.join(sdkSummaryDirectory, sdkSummarySoundFileName);
-    final soundFullDillPath =
-        p.join(sdkSummaryDirectory, sdkFullDillSoundFileName);
-    final soundJsPath = p.join(sdkJsDirectory, sdkJsSoundFileName);
-    final soundJsMapPath = p.join(sdkJsDirectory, sdkJsMapSoundFileName);
-
-    final weakSummaryPath = p.join(sdkSummaryDirectory, sdkSummaryWeakFileName);
-    final weakFullDillPath =
-        p.join(sdkSummaryDirectory, sdkFullDillWeakFileName);
-    final weakJsPath = p.join(sdkJsDirectory, sdkJsWeakFileName);
-    final weakJsMapPath = p.join(sdkJsDirectory, sdkJsMapWeakFileName);
-
-    final librariesPath = p.join(sdkDirectory, 'lib', 'libraries.json');
-    final dartdevcSnapshotPath =
-        p.join(sdkDirectory, 'bin', 'snapshots', 'dartdevc.dart.snapshot');
-    final kernelWorkerSnapshotPath =
-        p.join(sdkDirectory, 'bin', 'snapshots', 'kernel_worker.dart.snapshot');
-
-    return SdkLayout(
-      sdkJsWeakFileName: sdkJsWeakFileName,
-      sdkJsMapWeakFileName: sdkJsMapWeakFileName,
-      sdkJsSoundFileName: sdkJsSoundFileName,
-      sdkJsMapSoundFileName: sdkJsMapSoundFileName,
-      sdkSummarySoundFileName: sdkSummarySoundFileName,
-      sdkSummaryWeakFileName: sdkSummaryWeakFileName,
-      sdkFullDillSoundFileName: sdkFullDillSoundFileName,
-      sdkFullDillWeakFileName: sdkFullDillWeakFileName,
-      sdkDirectory: sdkDirectory,
-      soundSummaryPath: soundSummaryPath,
-      soundFullDillPath: soundFullDillPath,
-      soundJsPath: soundJsPath,
-      soundJsMapPath: soundJsMapPath,
-      weakSummaryPath: weakSummaryPath,
-      weakFullDillPath: weakFullDillPath,
-      weakJsPath: weakJsPath,
-      weakJsMapPath: weakJsMapPath,
-      librariesPath: librariesPath,
-      dartdevcSnapshotPath: dartdevcSnapshotPath,
-      kernelWorkerSnapshotPath: kernelWorkerSnapshotPath,
-    );
-  }
-
-  final String sdkJsWeakFileName;
-  final String sdkJsMapWeakFileName;
-  final String sdkJsSoundFileName;
-  final String sdkJsMapSoundFileName;
-  final String sdkSummarySoundFileName;
-  final String sdkSummaryWeakFileName;
-  final String sdkFullDillSoundFileName;
-  final String sdkFullDillWeakFileName;
+  static final defaultSdkDirectory =
+      p.dirname(p.dirname(Platform.resolvedExecutable));
+  static SdkLayout defaultSdkLayout =
+      SdkLayout.createDefault(defaultSdkDirectory);
 
   final String sdkDirectory;
-
   final String soundSummaryPath;
-  final String soundFullDillPath;
-  final String soundJsPath;
-  final String soundJsMapPath;
-
   final String weakSummaryPath;
-  final String weakFullDillPath;
-  final String weakJsPath;
-  final String weakJsMapPath;
-
-  final String librariesPath;
-
   final String dartdevcSnapshotPath;
-  final String kernelWorkerSnapshotPath;
 
-  SdkLayout({
-    required this.sdkJsWeakFileName,
-    required this.sdkJsMapWeakFileName,
-    required this.sdkJsSoundFileName,
-    required this.sdkJsMapSoundFileName,
-    required this.sdkSummarySoundFileName,
-    required this.sdkSummaryWeakFileName,
-    required this.sdkFullDillSoundFileName,
-    required this.sdkFullDillWeakFileName,
+  SdkLayout.createDefault(String sdkDirectory)
+      : this(
+          sdkDirectory: sdkDirectory,
+          soundSummaryPath: p.join(
+            sdkDirectory,
+            'lib',
+            '_internal',
+            'ddc_outline.dill',
+          ),
+          weakSummaryPath: p.join(
+            sdkDirectory,
+            'lib',
+            '_internal',
+            'ddc_outline_unsound.dill',
+          ),
+          dartdevcSnapshotPath: p.join(
+            sdkDirectory,
+            'bin',
+            'snapshots',
+            'dartdevc.dart.snapshot',
+          ),
+        );
+
+  const SdkLayout({
     required this.sdkDirectory,
     required this.soundSummaryPath,
-    required this.soundFullDillPath,
-    required this.soundJsPath,
-    required this.soundJsMapPath,
     required this.weakSummaryPath,
-    required this.weakFullDillPath,
-    required this.weakJsPath,
-    required this.weakJsMapPath,
-    required this.librariesPath,
     required this.dartdevcSnapshotPath,
-    required this.kernelWorkerSnapshotPath,
   });
 }
 
-/// Data class describing the SDK layout.
+/// Dart SDK configuration.
 ///
 /// Provides helpers to convert paths to uris that work on all platforms.
 ///
-/// Call [validate] method to make sure the files in the configuration
-/// layout exist before reading the files.
 class SdkConfiguration {
   static final defaultSdkLayout = SdkLayout.defaultSdkLayout;
   static final defaultConfiguration =
-      SdkConfiguration.fromSdkLayout(defaultSdkLayout);
+      SdkConfiguration.fromSdkLayout(SdkLayout.defaultSdkLayout);
 
-  String? sdkDirectory;
-  String? weakSdkSummaryPath;
-  String? soundSdkSummaryPath;
-  String? librariesPath;
-  String? compilerWorkerPath;
+  final String? sdkDirectory;
+  final String? weakSdkSummaryPath;
+  final String? soundSdkSummaryPath;
+  final String? compilerWorkerPath;
 
-  SdkConfiguration({
+  const SdkConfiguration({
     this.sdkDirectory,
     this.weakSdkSummaryPath,
     this.soundSdkSummaryPath,
-    this.librariesPath,
     this.compilerWorkerPath,
   });
 
-  SdkConfiguration.empty() : this();
+  const SdkConfiguration.empty() : this();
 
   SdkConfiguration.fromSdkLayout(SdkLayout sdkLayout)
       : this(
           sdkDirectory: sdkLayout.sdkDirectory,
           weakSdkSummaryPath: sdkLayout.weakSummaryPath,
           soundSdkSummaryPath: sdkLayout.soundSummaryPath,
-          librariesPath: sdkLayout.librariesPath,
           compilerWorkerPath: sdkLayout.dartdevcSnapshotPath,
         );
 
@@ -190,7 +115,6 @@
   Uri? get sdkDirectoryUri => _toUri(sdkDirectory);
   Uri? get soundSdkSummaryUri => _toUri(soundSdkSummaryPath);
   Uri? get weakSdkSummaryUri => _toUri(weakSdkSummaryPath);
-  Uri? get librariesUri => _toUri(librariesPath);
 
   /// Note: has to be ///file: Uri to run in an isolate.
   Uri? get compilerWorkerUri => _toAbsoluteUri(compilerWorkerPath);
@@ -200,7 +124,6 @@
   void validate({FileSystem fileSystem = const LocalFileSystem()}) {
     validateSdkDir(fileSystem: fileSystem);
     validateSummaries(fileSystem: fileSystem);
-    validateLibrariesSpec(fileSystem: fileSystem);
     validateCompilerWorker(fileSystem: fileSystem);
   }
 
@@ -237,14 +160,6 @@
     }
   }
 
-  void validateLibrariesSpec(
-      {FileSystem fileSystem = const LocalFileSystem()}) {
-    if (librariesPath == null || !fileSystem.file(librariesPath).existsSync()) {
-      throw InvalidSdkConfigurationException(
-          'Libraries spec $librariesPath does not exist');
-    }
-  }
-
   void validateCompilerWorker(
       {FileSystem fileSystem = const LocalFileSystem()}) {
     if (compilerWorkerPath == null ||
diff --git a/dwds/lib/src/utilities/server.dart b/dwds/lib/src/utilities/server.dart
new file mode 100644
index 0000000..923962f
--- /dev/null
+++ b/dwds/lib/src/utilities/server.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2019, 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:io';
+
+import 'package:dwds/src/services/chrome_debug_exception.dart';
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
+    as wip;
+
+/// Returns `true` if [hostname] is bound to an IPv6 address.
+Future<bool> useIPv6ForHost(String hostname) async {
+  final addresses = await InternetAddress.lookup(hostname);
+  if (addresses.isEmpty) return false;
+  final address = addresses.firstWhere(
+    (a) => a.type == InternetAddressType.IPv6,
+    orElse: () => addresses.first,
+  );
+  return address.type == InternetAddressType.IPv6;
+}
+
+/// Returns a port that is probably, but not definitely, not in use.
+///
+/// This has a built-in race condition: another process may bind this port at
+/// any time after this call has returned.
+Future<int> findUnusedPort() async {
+  int port;
+  ServerSocket socket;
+  try {
+    socket =
+        await ServerSocket.bind(InternetAddress.loopbackIPv6, 0, v6Only: true);
+  } on SocketException {
+    socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+  }
+  port = socket.port;
+  await socket.close();
+  return port;
+}
+
+/// Finds unused port and binds a new http server to it.
+///
+/// Retries a few times to recover from errors due to
+/// another thread or process opening the same port.
+/// Starts by trying to bind to [port] if specified.
+Future<HttpServer> startHttpServer(String hostname, {int? port}) async {
+  HttpServer? httpServer;
+  final retries = 5;
+  var i = 0;
+  var foundPort = port ?? await findUnusedPort();
+  while (i < retries) {
+    i++;
+    try {
+      httpServer = await HttpMultiServer.bind(hostname, foundPort);
+    } on SocketException {
+      if (i == retries) rethrow;
+    }
+    if (httpServer != null || i == retries) return httpServer!;
+    foundPort = await findUnusedPort();
+    await Future<void>.delayed(const Duration(milliseconds: 100));
+  }
+  return httpServer!;
+}
+
+/// Handles [requests] using [handler].
+///
+/// Captures all sync and async stack error traces and passes
+/// them to the [onError] handler.
+void serveHttpRequests(Stream<HttpRequest> requests, Handler handler,
+    void Function(Object, StackTrace) onError) {
+  return Chain.capture(() {
+    serveRequests(requests, handler);
+  }, onError: onError);
+}
+
+/// Throws an [wip.ExceptionDetails] object if `exceptionDetails` is present on the
+/// result.
+void handleErrorIfPresent(wip.WipResponse? response, {String? evalContents}) {
+  final result = response?.result;
+  if (result == null) return;
+  if (result.containsKey('exceptionDetails')) {
+    throw ChromeDebugException(
+      result['exceptionDetails'] as Map<String, dynamic>,
+      evalContents: evalContents,
+    );
+  }
+}
+
+/// Returns result contained in the response.
+/// Throws an [wip.ExceptionDetails] object if `exceptionDetails` is present on the
+/// result or the result is null.
+Map<String, dynamic> getResultOrHandleError(wip.WipResponse? response,
+    {String? evalContents}) {
+  handleErrorIfPresent(response, evalContents: evalContents);
+  final result = response?.result?['result'];
+  if (result == null) {
+    throw ChromeDebugException(
+      {'text': 'null result from Chrome Devtools'},
+      evalContents: evalContents,
+    );
+  }
+  return result;
+}
diff --git a/dwds/lib/src/utilities/shared.dart b/dwds/lib/src/utilities/shared.dart
index 7215fe6..df7e342 100644
--- a/dwds/lib/src/utilities/shared.dart
+++ b/dwds/lib/src/utilities/shared.dart
@@ -3,17 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:io';
 
-import 'package:http_multi_server/http_multi_server.dart';
-import 'package:shelf/shelf.dart';
-import 'package:shelf/shelf_io.dart';
-import 'package:stack_trace/stack_trace.dart';
+import 'package:logging/logging.dart';
 import 'package:vm_service/vm_service.dart';
-import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
-    as wip;
-
-import 'package:dwds/src/services/chrome_debug_exception.dart';
 
 VMRef toVMRef(VM vm) => VMRef(name: vm.name);
 
@@ -23,95 +15,13 @@
   return '$_nextId';
 }
 
-/// Returns `true` if [hostname] is bound to an IPv6 address.
-Future<bool> useIPv6ForHost(String hostname) async {
-  final addresses = await InternetAddress.lookup(hostname);
-  if (addresses.isEmpty) return false;
-  final address = addresses.firstWhere(
-    (a) => a.type == InternetAddressType.IPv6,
-    orElse: () => addresses.first,
-  );
-  return address.type == InternetAddressType.IPv6;
-}
+final _logger = Logger('Utilities');
 
-/// Returns a port that is probably, but not definitely, not in use.
-///
-/// This has a built-in race condition: another process may bind this port at
-/// any time after this call has returned.
-Future<int> findUnusedPort() async {
-  int port;
-  ServerSocket socket;
-  try {
-    socket =
-        await ServerSocket.bind(InternetAddress.loopbackIPv6, 0, v6Only: true);
-  } on SocketException {
-    socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
-  }
-  port = socket.port;
-  await socket.close();
-  return port;
-}
-
-/// Finds unused port and binds a new http server to it.
-///
-/// Retries a few times to recover from errors due to
-/// another thread or process opening the same port.
-/// Starts by trying to bind to [port] if specified.
-Future<HttpServer> startHttpServer(String hostname, {int? port}) async {
-  HttpServer? httpServer;
-  final retries = 5;
-  var i = 0;
-  var foundPort = port ?? await findUnusedPort();
-  while (i < retries) {
-    i++;
-    try {
-      httpServer = await HttpMultiServer.bind(hostname, foundPort);
-    } on SocketException {
-      if (i == retries) rethrow;
-    }
-    if (httpServer != null || i == retries) return httpServer!;
-    foundPort = await findUnusedPort();
-    await Future<void>.delayed(const Duration(milliseconds: 100));
-  }
-  return httpServer!;
-}
-
-/// Handles [requests] using [handler].
-///
-/// Captures all sync and async stack error traces and passes
-/// them to the [onError] handler.
-void serveHttpRequests(Stream<HttpRequest> requests, Handler handler,
-    void Function(Object, StackTrace) onError) {
-  return Chain.capture(() {
-    serveRequests(requests, handler);
-  }, onError: onError);
-}
-
-/// Throws an [wip.ExceptionDetails] object if `exceptionDetails` is present on the
-/// result.
-void handleErrorIfPresent(wip.WipResponse? response, {String? evalContents}) {
-  final result = response?.result;
-  if (result == null) return;
-  if (result.containsKey('exceptionDetails')) {
-    throw ChromeDebugException(
-      result['exceptionDetails'] as Map<String, dynamic>,
-      evalContents: evalContents,
-    );
-  }
-}
-
-/// Returns result contained in the response.
-/// Throws an [wip.ExceptionDetails] object if `exceptionDetails` is present on the
-/// result or the result is null.
-Map<String, dynamic> getResultOrHandleError(wip.WipResponse? response,
-    {String? evalContents}) {
-  handleErrorIfPresent(response, evalContents: evalContents);
-  final result = response?.result?['result'];
-  if (result == null) {
-    throw ChromeDebugException(
-      {'text': 'null result from Chrome Devtools'},
-      evalContents: evalContents,
-    );
-  }
-  return result;
+void safeUnawaited(
+  Future<void> future, {
+  void Function(dynamic, StackTrace)? onError,
+}) {
+  onError ??= (error, stackTrace) =>
+      _logger.warning('Error in unawaited Future:', error, stackTrace);
+  unawaited(future.catchError(onError));
 }
diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart
index 7b88465..a172ee8 100644
--- a/dwds/lib/src/version.dart
+++ b/dwds/lib/src/version.dart
@@ -1,2 +1,2 @@
 // Generated code. Do not modify.
-const packageVersion = '17.0.0';
+const packageVersion = '18.0.2';
diff --git a/dwds/lib/src/web_utilities/batched_stream.dart b/dwds/lib/src/web_utilities/batched_stream.dart
deleted file mode 100644
index 6da6465..0000000
--- a/dwds/lib/src/web_utilities/batched_stream.dart
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2022, 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 'package:async/async.dart';
-
-/// Stream controller allowing to batch events.
-class BatchedStreamController<T> {
-  static const _defaultBatchDelayMilliseconds = 1000;
-  static const _checkDelayMilliseconds = 100;
-
-  final int _batchDelayMilliseconds;
-
-  final StreamController<T> _inputController;
-  late StreamQueue<T> _inputQueue;
-
-  final StreamController<List<T>> _outputController;
-  final Completer<bool> _completer = Completer<bool>();
-
-  /// Create batched stream controller.
-  ///
-  /// Collects events from input [sink] and emits them in batches to the
-  /// output [stream] every [delay] milliseconds. Keeps the original order.
-  BatchedStreamController({
-    int delay = _defaultBatchDelayMilliseconds,
-  })  : _batchDelayMilliseconds = delay,
-        _inputController = StreamController<T>(),
-        _outputController = StreamController<List<T>>() {
-    _inputQueue = StreamQueue<T>(_inputController.stream);
-    unawaited(_batchAndSendEvents());
-  }
-
-  /// Sink collecting events.
-  StreamSink<T> get sink => _inputController.sink;
-
-  /// Output stream of batch events.
-  Stream<List<T>> get stream => _outputController.stream;
-
-  /// Close the controller.
-  Future<dynamic> close() async {
-    unawaited(_inputController.close());
-    return _completer.future.then((value) => _outputController.close());
-  }
-
-  /// Send events to the output in a batch every [_batchDelayMilliseconds].
-  Future<void> _batchAndSendEvents() async {
-    const duration = Duration(milliseconds: _checkDelayMilliseconds);
-    final buffer = <T>[];
-
-    // Batch events every `_batchDelayMilliseconds`.
-    //
-    // Note that events might arrive at random intervals, so collecting
-    // a predetermined number of events to send in a batch might delay
-    // the batch indefinitely.  Instead, check for new events every
-    // `_checkDelayMilliseconds` to make sure batches are sent in regular
-    // intervals.
-    var lastSendTime = DateTime.now().millisecondsSinceEpoch;
-    while (await _hasEventOrTimeOut(duration)) {
-      if (await _hasEventDuring(duration)) {
-        buffer.add(await _inputQueue.next);
-      }
-
-      final now = DateTime.now().millisecondsSinceEpoch;
-      if (now > lastSendTime + _batchDelayMilliseconds) {
-        lastSendTime = now;
-        if (buffer.isNotEmpty) {
-          _outputController.sink.add(List.from(buffer));
-          buffer.clear();
-        }
-      }
-    }
-
-    if (buffer.isNotEmpty) {
-      _outputController.sink.add(List.from(buffer));
-    }
-    _completer.complete(true);
-  }
-
-  Future<bool> _hasEventOrTimeOut(Duration duration) =>
-      _inputQueue.hasNext.timeout(duration, onTimeout: () => true);
-
-  Future<bool> _hasEventDuring(Duration duration) =>
-      _inputQueue.hasNext.timeout(duration, onTimeout: () => false);
-}
diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml
index 3a8d084..49d3b32 100644
--- a/dwds/pubspec.yaml
+++ b/dwds/pubspec.yaml
@@ -1,13 +1,13 @@
 name: dwds
 # Every time this changes you need to run `dart run build_runner build`.
-version: 17.0.0
+version: 18.0.2
 description: >-
   A service that proxies between the Chrome debug protocol and the Dart VM
   service protocol.
 repository: https://github.com/dart-lang/webdev/tree/master/dwds
 
 environment:
-  sdk: ">=3.0.0-134.0.dev <4.0.0"
+  sdk: ">=3.0.0-188.0.dev <4.0.0"
 
 dependencies:
   async: ^2.9.0
@@ -15,7 +15,7 @@
   built_value: ^8.3.0
   collection: ^1.15.0
   crypto: ^3.0.2
-  dds: ^2.2.5
+  dds: ^2.7.1
   file: ^6.1.3
   http: ^0.13.4
   http_multi_server: ^3.2.0
@@ -34,14 +34,14 @@
   stack_trace: ^1.10.0
   sse: ^4.1.2
   uuid: ^3.0.6
-  vm_service: ">=10.1.0 <12.0.0"
+  vm_service: ">=10.1.2 <12.0.0"
   web_socket_channel: ^2.2.0
   webkit_inspection_protocol: ^1.0.1
 
 dev_dependencies:
   args: ^2.3.1
   build: ^2.3.0
-  build_daemon: ^3.1.0
+  build_daemon: ^4.0.0
   build_runner: ^2.4.0
   build_version: ^2.1.1
   build_web_compilers: ^4.0.0
@@ -55,4 +55,6 @@
   puppeteer: ^2.19.0
   stream_channel: ^2.1.0
   test: ^1.21.1
+  test_common:
+    path: ../test_common
   webdriver: ^3.0.0
diff --git a/dwds/web/client.dart b/dwds/web/client.dart
index 61e1cdd..1a52bf0 100644
--- a/dwds/web/client.dart
+++ b/dwds/web/client.dart
@@ -17,15 +17,12 @@
 import 'package:dwds/data/debug_info.dart';
 import 'package:dwds/data/devtools_request.dart';
 import 'package:dwds/data/error_response.dart';
+import 'package:dwds/data/extension_request.dart';
 import 'package:dwds/data/register_event.dart';
 import 'package:dwds/data/run_request.dart';
 import 'package:dwds/data/serializers.dart';
+import 'package:dwds/shared/batched_stream.dart';
 import 'package:dwds/src/sockets.dart';
-// NOTE(annagrin): using 'package:dwds/src/utilities/batched_stream.dart'
-// makes dart2js skip creating background.js, so we use a copy instead.
-// import 'package:dwds/src/utilities/batched_stream.dart';
-// Issue: https://github.com/dart-lang/sdk/issues/49973
-import 'package:dwds/src/web_utilities/batched_stream.dart';
 import 'package:js/js.dart';
 import 'package:sse/client/sse_client.dart';
 import 'package:uuid/uuid.dart';
@@ -173,18 +170,7 @@
       // If not Chromium we just invoke main, devtools aren't supported.
       runMain();
     }
-    final windowContext = JsObject.fromBrowserObject(window);
-    final debugInfoJson = jsonEncode(serializers.serialize(DebugInfo((b) => b
-      ..appEntrypointPath = dartEntrypointPath
-      ..appId = windowContext['\$dartAppId']
-      ..appInstanceId = dartAppInstanceId
-      ..appOrigin = window.location.origin
-      ..appUrl = window.location.href
-      ..extensionUrl = windowContext['\$dartExtensionUri']
-      ..isInternalBuild = windowContext['\$isInternalBuild']
-      ..isFlutterApp = windowContext['\$isFlutterApp'])));
-
-    dispatchEvent(CustomEvent('dart-app-ready', detail: debugInfoJson));
+    _launchCommunicationWithDebugExtension();
   }, (error, stackTrace) {
     print('''
 Unhandled error detected in the injected client.js script.
@@ -231,6 +217,51 @@
   return uri.toString();
 }
 
+void _launchCommunicationWithDebugExtension() {
+  // Listen for an event from the Dart Debug Extension to authenticate the
+  // user (sent once the extension receives the dart-app-read event):
+  _listenForDebugExtensionAuthRequest();
+
+  // Send the dart-app-ready event along with debug info to the Dart Debug
+  // Extension so that it can debug the Dart app:
+  final debugInfoJson = jsonEncode(serializers.serialize(DebugInfo((b) => b
+    ..appEntrypointPath = dartEntrypointPath
+    ..appId = _appId
+    ..appInstanceId = dartAppInstanceId
+    ..appOrigin = window.location.origin
+    ..appUrl = window.location.href
+    ..authUrl = _authUrl
+    ..extensionUrl = _extensionUrl
+    ..isInternalBuild = _isInternalBuild
+    ..isFlutterApp = _isFlutterApp)));
+  dispatchEvent(CustomEvent('dart-app-ready', detail: debugInfoJson));
+}
+
+void _listenForDebugExtensionAuthRequest() {
+  window.addEventListener('message', allowInterop((event) async {
+    final messageEvent = event as MessageEvent;
+    if (messageEvent.data is! String) return;
+    if (messageEvent.data as String != 'dart-auth-request') return;
+
+    // Notify the Dart Debug Extension of authentication status:
+    if (_authUrl != null) {
+      final isAuthenticated = await _authenticateUser(_authUrl!);
+      dispatchEvent(
+          CustomEvent('dart-auth-response', detail: '$isAuthenticated'));
+    }
+  }));
+}
+
+Future<bool> _authenticateUser(String authUrl) async {
+  final response = await HttpRequest.request(
+    authUrl,
+    method: 'GET',
+    withCredentials: true,
+  );
+  final responseText = response.responseText ?? '';
+  return responseText.contains('Dart Debug Authentication Success!');
+}
+
 @JS(r'$dartAppId')
 external String get dartAppId;
 
@@ -283,3 +314,27 @@
 external bool get isFlutterApp;
 
 bool get _isChromium => window.navigator.vendor.contains('Google');
+
+JsObject get _windowContext => JsObject.fromBrowserObject(window);
+
+bool? get _isInternalBuild => _windowContext['\$isInternalBuild'];
+
+bool? get _isFlutterApp => _windowContext['\$isFlutterApp'];
+
+String? get _appId => _windowContext['\$dartAppId'];
+
+String? get _extensionUrl => _windowContext['\$dartExtensionUri'];
+
+String? get _authUrl {
+  final extensionUrl = _extensionUrl;
+  if (extensionUrl == null) return null;
+  final authUrl = Uri.parse(extensionUrl).replace(path: authenticationPath);
+  switch (authUrl.scheme) {
+    case 'ws':
+      return authUrl.replace(scheme: 'http').toString();
+    case 'wss':
+      return authUrl.replace(scheme: 'https').toString();
+    default:
+      return authUrl.toString();
+  }
+}
diff --git a/dwds/web/promise.dart b/dwds/web/promise.dart
index 30e76e4..fdc2d4d 100644
--- a/dwds/web/promise.dart
+++ b/dwds/web/promise.dart
@@ -47,7 +47,8 @@
   final completer = Completer<T>();
   promise.then(
     allowInterop(completer.complete),
-    allowInterop((e) => completer.completeError(e)),
+    // TODO(annagrin): propagate stack trace from promise instead.
+    allowInterop((e) => completer.completeError(e, StackTrace.current)),
   );
   return completer.future;
 }
diff --git a/dwds/web/reloader/manager.dart b/dwds/web/reloader/manager.dart
index a9d568a..adb8e37 100644
--- a/dwds/web/reloader/manager.dart
+++ b/dwds/web/reloader/manager.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:convert';
 import 'dart:html';
 
diff --git a/dwds/web/reloader/require_restarter.dart b/dwds/web/reloader/require_restarter.dart
index 6124c55..558925c 100644
--- a/dwds/web/reloader/require_restarter.dart
+++ b/dwds/web/reloader/require_restarter.dart
@@ -221,9 +221,7 @@
     final stackTrace = StackTrace.current;
     requireLoader.forceLoadModule(
       moduleId,
-      allowInterop(() {
-        completer.complete();
-      }),
+      allowInterop(completer.complete),
       allowInterop((e) {
         completer.completeError(
             HotReloadFailedException(e.message), stackTrace);
diff --git a/dwds/web/run_main.dart b/dwds/web/run_main.dart
index bee8731..23f490e 100644
--- a/dwds/web/run_main.dart
+++ b/dwds/web/run_main.dart
@@ -9,7 +9,7 @@
 /// More specifically, the script has the correct `nonce` value set.
 final ScriptElement Function() _createScript = (() {
   final nonce = _findNonce();
-  if (nonce == null) return () => ScriptElement();
+  if (nonce == null) return ScriptElement.new;
 
   return () => ScriptElement()..setAttribute('nonce', nonce);
 })();
diff --git a/ffi/BUILD.gn b/ffi/BUILD.gn
index c081450..cf92e58 100644
--- a/ffi/BUILD.gn
+++ b/ffi/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for ffi-2.0.1
+# This file is generated by package_importer.py for ffi-2.0.2
 
 import("//build/dart/dart_library.gni")
 
diff --git a/ffi/CHANGELOG.md b/ffi/CHANGELOG.md
index 4ffcbbb..6ecb903 100644
--- a/ffi/CHANGELOG.md
+++ b/ffi/CHANGELOG.md
@@ -1,15 +1,18 @@
-# Changelog
+## 2.0.2
+
+- Fixed a typo in a doc comment.
+- Added package topics to the pubspec file.
 
 ## 2.0.1
 
-Only zero out memory on successful allocation on Windows.
-Upgrade test dev dependency.
+- Only zero out memory on successful allocation on Windows.
+- Upgrade test dev dependency.
 
 ## 2.0.0
 
-Switch Windows memory allocation to use `CoTaskMemAlloc` and `CoTaskMemFree`,
-which will enable support for `NativeFinalizer`. This release requires Dart
-2.17.0 or greater.
+- Switch Windows memory allocation to use `CoTaskMemAlloc` and `CoTaskMemFree`,
+  which will enable support for `NativeFinalizer`.
+- Require Dart 2.17.0 or greater.
 
 ## 1.2.1
 
diff --git a/ffi/README.md b/ffi/README.md
index 728827c..8a9255c 100644
--- a/ffi/README.md
+++ b/ffi/README.md
@@ -5,5 +5,7 @@
 Utilities for working with Foreign Function Interface (FFI) code, incl.
 converting between Dart strings and C strings encoded with UTF-8 and UTF-16.
 
+Please see the [API reference](https://pub.dev/documentation/ffi/latest/ffi/ffi-library.html) for more documentation and the [tests](https://github.com/dart-lang/ffi/tree/master/test) for example usage. 
+
 For additional details about Dart FFI (`dart:ffi`), see
 https://dart.dev/guides/libraries/c-interop.
diff --git a/ffi/lib/src/arena.dart b/ffi/lib/src/arena.dart
index cc6fd0b..747d120 100644
--- a/ffi/lib/src/arena.dart
+++ b/ffi/lib/src/arena.dart
@@ -136,7 +136,7 @@
 
 /// Creates a zoned [Arena] to manage native resources.
 ///
-/// The arena is availabe through [zoneArena].
+/// The arena is available through [zoneArena].
 ///
 /// If the isolate is shut down, through `Isolate.kill()`, resources are _not_
 /// cleaned up.
diff --git a/ffi/pubspec.yaml b/ffi/pubspec.yaml
index 33c792c..ac21a95 100644
--- a/ffi/pubspec.yaml
+++ b/ffi/pubspec.yaml
@@ -1,10 +1,15 @@
 name: ffi
-version: 2.0.1
+version: 2.0.2
 description: Utilities for working with Foreign Function Interface (FFI) code.
 repository: https://github.com/dart-lang/ffi
 
+topics:
+ - interop
+ - ffi
+ - codegen
+
 environment:
-  sdk: '>=2.17.0 <3.0.0'
+  sdk: '>=2.17.0 <4.0.0'
 
 dev_dependencies:
   test: ^1.21.2
diff --git a/flutter_image/BUILD.gn b/flutter_image/BUILD.gn
index 76002c0..a233249 100644
--- a/flutter_image/BUILD.gn
+++ b/flutter_image/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for flutter_image-4.1.5
+# This file is generated by package_importer.py for flutter_image-4.1.6
 
 import("//build/dart/dart_library.gni")
 
 dart_library("flutter_image") {
   package_name = "flutter_image"
 
-  language_version = "2.12"
+  language_version = "2.18"
 
   disable_analysis = true
 
diff --git a/flutter_image/CHANGELOG.md b/flutter_image/CHANGELOG.md
index 1a2b63e..411b592 100644
--- a/flutter_image/CHANGELOG.md
+++ b/flutter_image/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 4.1.6
+
+* Fixes unawaited_futures violations.
+* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.
+* Aligns Dart and Flutter SDK constraints.
+
 ## 4.1.5
 
 * Removes use of `runtimeType.toString()`.
diff --git a/flutter_image/lib/network.dart b/flutter_image/lib/network.dart
index 841d9ad..e9ae3f0 100644
--- a/flutter_image/lib/network.dart
+++ b/flutter_image/lib/network.dart
@@ -191,7 +191,7 @@
           scale: key.scale,
         );
       } catch (error) {
-        request?.close();
+        await request?.close();
         lastFailure = error is FetchFailure
             ? error
             : FetchFailure._(
diff --git a/flutter_image/pubspec.yaml b/flutter_image/pubspec.yaml
index 340d655..0990161 100644
--- a/flutter_image/pubspec.yaml
+++ b/flutter_image/pubspec.yaml
@@ -3,11 +3,11 @@
   Image utilities for Flutter: improved network providers, effects, etc.
 repository: https://github.com/flutter/packages/tree/main/packages/flutter_image
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_image%22
-version: 4.1.5
+version: 4.1.6
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
-  flutter: ">=3.0.0"
+  sdk: ">=2.18.0 <4.0.0"
+  flutter: ">=3.3.0"
 
 dependencies:
   flutter:
diff --git a/gcloud/BUILD.gn b/gcloud/BUILD.gn
index 8724b4f..3a94be9 100644
--- a/gcloud/BUILD.gn
+++ b/gcloud/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for gcloud-0.8.7
+# This file is generated by package_importer.py for gcloud-0.8.9
 
 import("//build/dart/dart_library.gni")
 
 dart_library("gcloud") {
   package_name = "gcloud"
 
-  language_version = "2.12"
+  language_version = "2.19"
 
   disable_analysis = true
 
diff --git a/gcloud/CHANGELOG.md b/gcloud/CHANGELOG.md
index 71ade45..a93af8f 100644
--- a/gcloud/CHANGELOG.md
+++ b/gcloud/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 0.8.9
+
+- Support the latest version 1.0.0 of the `http` package.
+- Support the latest version 12.0.0 of the `googleapis` package.
+
+## 0.8.8
+
+- Require Dart 2.19
+- Add topics in `pubspec.yaml`.
+
 ## 0.8.7
 
 - Fix `Bucket.write` when size is below 1MB.
diff --git a/gcloud/analysis_options.yaml b/gcloud/analysis_options.yaml
index 8981086..ecdba56 100644
--- a/gcloud/analysis_options.yaml
+++ b/gcloud/analysis_options.yaml
@@ -1,23 +1,12 @@
-include: package:lints/recommended.yaml
+include: package:dart_flutter_team_lints/analysis_options.yaml
 
 analyzer:
-  strong-mode:
-    implicit-casts: false
+  language:
+    strict-casts: true
 
 linter:
   rules:
-    - await_only_futures
-    - camel_case_types
     - cancel_subscriptions
-    - control_flow_in_finally
-    - directives_ordering
-    - empty_statements
-    - iterable_contains_unrelated_type
-    - list_remove_unrelated_type
     - package_api_docs
-    - package_names
-    - package_prefixed_library_names
     - prefer_relative_imports
     - test_types_in_equals
-    - throw_in_finally
-    - unnecessary_brace_in_string_interps
diff --git a/gcloud/lib/common.dart b/gcloud/lib/common.dart
index 09e3bcd..e564f51 100644
--- a/gcloud/lib/common.dart
+++ b/gcloud/lib/common.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library gcloud.common;
-
 import 'dart:async';
 
 /// A single page of paged results from a query.
diff --git a/gcloud/lib/datastore.dart b/gcloud/lib/datastore.dart
index b1c857b..3b05eea 100644
--- a/gcloud/lib/datastore.dart
+++ b/gcloud/lib/datastore.dart
@@ -7,7 +7,7 @@
 ///
 /// For more information on Cloud Datastore, please refer to the following
 /// developers page: https://cloud.google.com/datastore/docs
-library gcloud.datastore;
+library;
 
 import 'dart:async';
 
@@ -52,7 +52,7 @@
   final String message;
 
   DatastoreError([String? message])
-      : message = (message ?? 'DatastoreError: An unknown error occured');
+      : message = message ?? 'DatastoreError: An unknown error occured';
 
   @override
   String toString() => message;
diff --git a/gcloud/lib/db.dart b/gcloud/lib/db.dart
index 11509e0..6996f6f 100644
--- a/gcloud/lib/db.dart
+++ b/gcloud/lib/db.dart
@@ -2,7 +2,7 @@
 // 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.
 
-library gcloud.db;
+library;
 
 import 'dart:collection';
 // dart:core is imported explicitly so it is available at top-level without
diff --git a/gcloud/lib/db/metamodel.dart b/gcloud/lib/db/metamodel.dart
index c019fd2..cc8bab4 100644
--- a/gcloud/lib/db/metamodel.dart
+++ b/gcloud/lib/db/metamodel.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library gcloud.db.meta_model;
-
 import '../db.dart' as db;
 
 @db.Kind(name: '__namespace__')
diff --git a/gcloud/lib/http.dart b/gcloud/lib/http.dart
index 0ba6cdc..30fd7ae 100644
--- a/gcloud/lib/http.dart
+++ b/gcloud/lib/http.dart
@@ -4,7 +4,7 @@
 
 /// Provides access to an authenticated HTTP client which can be used to access
 /// Google APIs.
-library gcloud.http;
+library;
 
 import 'package:http/http.dart' as http;
 
diff --git a/gcloud/lib/pubsub.dart b/gcloud/lib/pubsub.dart
index a295115..42551f8 100644
--- a/gcloud/lib/pubsub.dart
+++ b/gcloud/lib/pubsub.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library gcloud.pubsub;
-
 import 'dart:async';
 import 'dart:collection';
 import 'dart:convert';
diff --git a/gcloud/lib/service_scope.dart b/gcloud/lib/service_scope.dart
index 8976870..6e23c47 100644
--- a/gcloud/lib/service_scope.dart
+++ b/gcloud/lib/service_scope.dart
@@ -72,7 +72,7 @@
 /// and instead depend only on the services needed (e.g.
 /// `package:gcloud/storage.dart`) by using getters in the service library (e.g.
 /// the `storageService`) which are implemented with service scope lookups.
-library gcloud.service_scope;
+library;
 
 import 'dart:async';
 
diff --git a/gcloud/lib/src/datastore_impl.dart b/gcloud/lib/src/datastore_impl.dart
index fbe7c03..d62868c 100644
--- a/gcloud/lib/src/datastore_impl.dart
+++ b/gcloud/lib/src/datastore_impl.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library gcloud.datastore_impl;
-
 import 'dart:async';
 
 import 'package:googleapis/datastore/v1.dart' as api;
diff --git a/gcloud/lib/src/db/annotations.dart b/gcloud/lib/src/db/annotations.dart
index 241d579..de895bc 100644
--- a/gcloud/lib/src/db/annotations.dart
+++ b/gcloud/lib/src/db/annotations.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of gcloud.db;
+part of '../../db.dart';
 
 /// Annotation used to mark dart classes which can be stored into datastore.
 ///
diff --git a/gcloud/lib/src/db/db.dart b/gcloud/lib/src/db/db.dart
index 1d7eda8..9a4bbf3 100644
--- a/gcloud/lib/src/db/db.dart
+++ b/gcloud/lib/src/db/db.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of gcloud.db;
+part of '../../db.dart';
 
 /// A function definition for transactional functions.
 ///
@@ -205,8 +205,9 @@
 
   /// Adds an order to this [Query].
   ///
-  /// [orderString] has the form "-name" where 'name' is a fieldName of the model
-  /// and the optional '-' says whether the order is descending or ascending.
+  /// [orderString] has the form "-name" where 'name' is a fieldName of the
+  /// model and the optional '-' says whether the order is descending or
+  /// ascending.
   void order(String orderString) {
     // TODO: validate [orderString] (e.g. is name valid)
     if (orderString.startsWith('-')) {
@@ -400,6 +401,9 @@
   /// direct lookups will see the effect but non-ancestor queries will see the
   /// change in an eventual consistent way.
   ///
+  /// The inserts are done as upserts unless the provided model does not have an
+  /// id, in which case an autoId will be generated.
+  ///
   /// For transactions, please use `beginTransaction` and it's returned
   /// [Transaction] object.
   Future commit({List<Model>? inserts, List<Key>? deletes}) {
diff --git a/gcloud/lib/src/db/exceptions.dart b/gcloud/lib/src/db/exceptions.dart
index 11c48b1..6eed41d 100644
--- a/gcloud/lib/src/db/exceptions.dart
+++ b/gcloud/lib/src/db/exceptions.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of gcloud.db;
+part of '../../db.dart';
 
 /// Exception that gets thrown when a caller attempts to look up a value by
 /// its key, and the key cannot be found in the datastore.
diff --git a/gcloud/lib/src/db/model_db.dart b/gcloud/lib/src/db/model_db.dart
index ba19cae..dd30c1b 100644
--- a/gcloud/lib/src/db/model_db.dart
+++ b/gcloud/lib/src/db/model_db.dart
@@ -2,11 +2,12 @@
 // 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.
 
-part of gcloud.db;
+part of '../../db.dart';
 
 /// A database of all registered models.
 ///
-/// Responsible for converting between dart model objects and datastore entities.
+/// Responsible for converting between dart model objects and datastore
+/// entities.
 abstract class ModelDB {
   /// Converts a [ds.Key] to a [Key].
   Key fromDatastoreKey(ds.Key datastoreKey);
diff --git a/gcloud/lib/src/db/model_db_impl.dart b/gcloud/lib/src/db/model_db_impl.dart
index e17ddcf..0703ec5 100644
--- a/gcloud/lib/src/db/model_db_impl.dart
+++ b/gcloud/lib/src/db/model_db_impl.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of gcloud.db;
+part of '../../db.dart';
 
 /// An implementation of [ModelDB] based on model class annotations.
 ///
@@ -202,9 +202,7 @@
       lm.declarations.values
           .whereType<mirrors.ClassMirror>()
           .where((d) => d.hasReflectedType)
-          .forEach((declaration) {
-        _tryLoadNewModelClass(declaration);
-      });
+          .forEach(_tryLoadNewModelClass);
     }
 
     // Ask every [ModelDescription] to compute whatever global state it wants
@@ -228,7 +226,7 @@
   void _tryLoadNewModelClass(mirrors.ClassMirror classMirror) {
     Kind? kindAnnotation;
     for (var instance in classMirror.metadata) {
-      if (instance.reflectee.runtimeType == Kind) {
+      if ((instance.reflectee as Object).runtimeType == Kind) {
         if (kindAnnotation != null) {
           throw StateError(
               'Cannot have more than one ModelMetadata() annotation '
@@ -461,8 +459,9 @@
     var mirror = classMirror.newInstance(const Symbol(''), []);
 
     // Set the id and the parent key
-    mirror.reflectee.id = key.id;
-    mirror.reflectee.parentKey = key.parent;
+    final model = mirror.reflectee as Model;
+    model.id = key.id;
+    model.parentKey = key.parent;
 
     db._propertiesForModel(this).forEach((String fieldName, Property prop) {
       _decodeProperty(db, entity, mirror, fieldName, prop);
@@ -485,9 +484,12 @@
 
     try {
       mirror.setField(mirrors.MirrorSystem.getSymbol(fieldName), value);
+      // ignore: avoid_catching_errors
     } on TypeError catch (error) {
-      throw StateError('Error trying to set property "${prop.propertyName}" '
-          'to $value for field "$fieldName": $error');
+      throw StateError(
+        'Error trying to set property "${prop.propertyName}" '
+        'to $value for field "$fieldName": $error',
+      );
     }
   }
 
diff --git a/gcloud/lib/src/db/models.dart b/gcloud/lib/src/db/models.dart
index 96a1b03..384e9f5 100644
--- a/gcloud/lib/src/db/models.dart
+++ b/gcloud/lib/src/db/models.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of gcloud.db;
+part of '../../db.dart';
 
 /// Represents a unique identifier for a [Model] stored in a datastore.
 ///
@@ -97,8 +97,9 @@
 
 /// Superclass for all model classes.
 ///
-/// Every model class has a [id] of type [T] which must be `int` or `String`, and
-/// a [parentKey]. The [key] getter is returning the key for the model object.
+/// Every model class has a [id] of type [T] which must be `int` or `String`,
+/// and a [parentKey]. The [key] getter is returning the key for the model
+/// object.
 abstract class Model<T> {
   T? id;
   Key? parentKey;
diff --git a/gcloud/lib/src/pubsub_impl.dart b/gcloud/lib/src/pubsub_impl.dart
index a546cfc..ce491b1 100644
--- a/gcloud/lib/src/pubsub_impl.dart
+++ b/gcloud/lib/src/pubsub_impl.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of gcloud.pubsub;
+part of '../pubsub.dart';
 
 class _PubSubImpl implements PubSub {
   @override
@@ -337,14 +337,14 @@
 
   factory _PushEventImpl.fromJson(String json) {
     Map body = jsonDecode(json) as Map<String, dynamic>;
-    var data = body['message']['data'] as String;
+    var data = (body['message'] as Map)['data'] as String;
     Map<String, String> labels = HashMap();
-    body['message']['labels'].forEach((label) {
-      var key = label['key'] as String;
-      var value = label['strValue'];
-      value ??= label['numValue'];
+    for (var label in (body['message'] as Map)['labels'] as List) {
+      final l = label as Map;
+      var key = l['key'] as String;
+      var value = l['strValue'] ?? l['numValue'];
       labels[key] = value.toString();
-    });
+    }
     var subscription = body['subscription'] as String;
     // TODO(#1): Remove this when the push event subscription name is prefixed
     // with '/subscriptions/'.
diff --git a/gcloud/lib/src/storage_impl.dart b/gcloud/lib/src/storage_impl.dart
index 6251c0c..d2850d9 100644
--- a/gcloud/lib/src/storage_impl.dart
+++ b/gcloud/lib/src/storage_impl.dart
@@ -2,7 +2,7 @@
 // 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.
 
-part of gcloud.storage;
+part of '../storage.dart';
 
 const String _absolutePrefix = 'gs://';
 const String _directoryDelimiter = '/';
@@ -85,7 +85,7 @@
   Future<BucketInfo> bucketInfo(String bucketName) {
     return _api.buckets
         .get(bucketName, projection: 'full')
-        .then((bucket) => _BucketInfoImpl(bucket));
+        .then(_BucketInfoImpl.new);
   }
 
   @override
@@ -555,7 +555,7 @@
   }
 
   @override
-  void addError(errorEvent, [StackTrace? stackTrace]) {
+  void addError(Object errorEvent, [StackTrace? stackTrace]) {
     _controller.addError(errorEvent, stackTrace);
   }
 
diff --git a/gcloud/lib/storage.dart b/gcloud/lib/storage.dart
index 122e395..3c726f2 100644
--- a/gcloud/lib/storage.dart
+++ b/gcloud/lib/storage.dart
@@ -46,7 +46,7 @@
 /// For most of the APIs in ths library which take instances of other classes
 /// from this library it is the assumption that the actual implementations
 /// provided here are used.
-library gcloud.storage;
+library;
 
 import 'dart:async';
 import 'dart:collection' show UnmodifiableListView, UnmodifiableMapView;
@@ -87,21 +87,6 @@
   ss.register(_storageKey, storage);
 }
 
-int _jenkinsHash(List e) {
-  const _hashMask = 0x3fffffff;
-  var hash = 0;
-  for (var i = 0; i < e.length; i++) {
-    var c = e[i].hashCode;
-    hash = (hash + c) & _hashMask;
-    hash = (hash + (hash << 10)) & _hashMask;
-    hash ^= (hash >> 6);
-  }
-  hash = (hash + (hash << 3)) & _hashMask;
-  hash ^= (hash >> 11);
-  hash = (hash + (hash << 15)) & _hashMask;
-  return hash;
-}
-
 /// An ACL (Access Control List) describes access rights to buckets and
 /// objects.
 ///
@@ -176,7 +161,7 @@
   }
 
   @override
-  late final int hashCode = _jenkinsHash(_entries);
+  late final int hashCode = Object.hashAll(_entries);
 
   @override
   bool operator ==(Object other) {
@@ -221,7 +206,7 @@
   }
 
   @override
-  late final int hashCode = _jenkinsHash([scope, permission]);
+  late final int hashCode = Object.hash(scope, permission);
 
   @override
   bool operator ==(Object other) {
@@ -287,7 +272,7 @@
   AclScope._(this._type, this._id);
 
   @override
-  late final int hashCode = _jenkinsHash([_type, _id]);
+  late final int hashCode = Object.hash(_type, _id);
 
   @override
   bool operator ==(Object other) {
diff --git a/gcloud/pubspec.yaml b/gcloud/pubspec.yaml
index 3cf8142..676df66 100644
--- a/gcloud/pubspec.yaml
+++ b/gcloud/pubspec.yaml
@@ -1,21 +1,24 @@
 name: gcloud
-version: 0.8.7
+version: 0.8.9
 description: >-
   High level idiomatic Dart API for Google Cloud Storage, Pub-Sub and Datastore.
 repository: https://github.com/dart-lang/gcloud
+topics:
+ - cloud
+ - gcp
 
 environment:
-  sdk: '>=2.12.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
   _discoveryapis_commons: ^1.0.0
-  googleapis: '>=3.0.0 <10.0.0'
-  http: ^0.13.0
+  googleapis: '>=3.0.0 <12.0.0'
+  http: '>=0.13.5 <2.0.0'
   meta: ^1.3.0
 
 dev_dependencies:
+  dart_flutter_team_lints: ^1.0.0
   googleapis_auth: ^1.1.0
   http_parser: ^4.0.0
-  lints: ^1.0.0
   mime: ^1.0.0
   test: ^1.17.5
diff --git a/googleapis_auth/BUILD.gn b/googleapis_auth/BUILD.gn
index e8f83ed..ec01f51 100644
--- a/googleapis_auth/BUILD.gn
+++ b/googleapis_auth/BUILD.gn
@@ -1,18 +1,20 @@
-# This file is generated by package_importer.py for googleapis_auth-1.3.1
+# This file is generated by package_importer.py for googleapis_auth-1.4.1
 
 import("//build/dart/dart_library.gni")
 
 dart_library("googleapis_auth") {
   package_name = "googleapis_auth"
 
-  language_version = "2.13"
+  language_version = "2.19"
 
   disable_analysis = true
 
   deps = [
+    "//third_party/dart-pkg/pub/args",
     "//third_party/dart-pkg/pub/crypto",
     "//third_party/dart-pkg/pub/http",
     "//third_party/dart-pkg/pub/http_parser",
+    "//third_party/dart-pkg/pub/js",
   ]
 
   sources = [
@@ -26,6 +28,8 @@
     "src/auth_client.dart",
     "src/auth_functions.dart",
     "src/auth_http_utils.dart",
+    "src/authentication_exception.dart",
+    "src/browser_utils.dart",
     "src/client_id.dart",
     "src/crypto/asn1.dart",
     "src/crypto/pem.dart",
@@ -43,6 +47,8 @@
     "src/oauth2_flows/implicit.dart",
     "src/oauth2_flows/jwt.dart",
     "src/oauth2_flows/metadata_server.dart",
+    "src/oauth2_flows/token_model.dart",
+    "src/oauth2_flows/token_model_interop.dart",
     "src/response_type.dart",
     "src/service_account_client.dart",
     "src/service_account_credentials.dart",
diff --git a/googleapis_auth/CHANGELOG.md b/googleapis_auth/CHANGELOG.md
index 4abccfa..54bca85 100644
--- a/googleapis_auth/CHANGELOG.md
+++ b/googleapis_auth/CHANGELOG.md
@@ -1,3 +1,32 @@
+## 1.4.1
+
+- Require Dart 2.19 or later.
+- Allow latest `package:http`.
+
+## 1.4.0
+
+- Update `README` to include a warning about Flutter application usage.
+- Require Dart 2.17 or later.
+
+#### `googlapis_auth.dart`
+
+- `authenticatedClient` function: added optional `bool closeUnderlyingClient`
+  parameter.
+
+#### `auth_browser.dart` library
+
+- Added `AuthenticationException` and use it instead of `Exception` or
+  `StateError` in many cases where authentication can fail.
+- Added `requestAccessCredentials`, `requestAuthorizationCode`, `revokeConsent`,
+  and `CodeResponse` to support the new
+  [Google Identity Services](https://developers.google.com/identity/oauth2/web/guides/overview).
+- Deprecated `createImplicitBrowserFlow` function.
+
+#### `auth_io.dart` library
+
+- Added an optional `listenPort` parameter to `clientViaUserConsent`
+  and `obtainAccessCredentialsViaUserConsent`. 
+
 ## 1.3.1
 
 - Include `plugin_name` during browser authorization.
diff --git a/googleapis_auth/README.md b/googleapis_auth/README.md
index 0b2330c..6af278e 100644
--- a/googleapis_auth/README.md
+++ b/googleapis_auth/README.md
@@ -5,6 +5,13 @@
 - obtaining authenticated HTTP clients
 - automatically refreshing OAuth2 credentials
 
+> Do _**NOT**_ use this package (`package:googleapis_auth`) with a
+> [Flutter](https://flutter.dev/) application.
+>
+> Use
+> [package:extension_google_sign_in_as_googleapis_auth](https://pub.dev/packages/extension_google_sign_in_as_googleapis_auth)
+> instead.
+
 ### Using this package
 
 Using this package requires creating a Google Cloud Project and obtaining
diff --git a/googleapis_auth/lib/auth_browser.dart b/googleapis_auth/lib/auth_browser.dart
index 67800c6..d5fbf27 100644
--- a/googleapis_auth/lib/auth_browser.dart
+++ b/googleapis_auth/lib/auth_browser.dart
@@ -11,12 +11,25 @@
 import 'src/auth_http_utils.dart';
 import 'src/http_client_base.dart';
 import 'src/oauth2_flows/implicit.dart';
+import 'src/oauth2_flows/token_model.dart';
 import 'src/service_account_credentials.dart';
 
 export 'googleapis_auth.dart';
+export 'src/authentication_exception.dart' show AuthenticationException;
+export 'src/oauth2_flows/token_model.dart'
+    show
+        CodeResponse,
+        requestAccessCredentials,
+        requestAuthorizationCode,
+        revokeConsent;
 
 /// Will create and complete with a [BrowserOAuth2Flow] object.
 ///
+/// {@template googleapis_auth_gis_deprecated}
+/// This member is *deprecated*. Use [requestAccessCredentials] or
+/// [requestAuthorizationCode] instead.
+/// {@endtemplate}
+///
 /// This function will perform an implicit browser based oauth2 flow.
 ///
 /// It will load Google's `gapi` library and initialize it. After initialization
@@ -35,6 +48,10 @@
 ///
 /// {@macro googleapis_auth_close_the_client}
 /// {@macro googleapis_auth_not_close_the_baseClient}
+@Deprecated(
+  'This member is deprecated. Use requestAccessCredentials or '
+  'requestAuthorizationCode instead.',
+)
 Future<BrowserOAuth2Flow> createImplicitBrowserFlow(
   ClientId clientId,
   List<String> scopes, {
@@ -43,7 +60,7 @@
     'Undocumented feature. May help debugging. '
     'Do not include in production code.',
   )
-      bool enableDebugLogs = false,
+  bool enableDebugLogs = false,
 }) async {
   final refCountedClient = baseClient == null
       ? RefCountedClient(BrowserClient())
@@ -62,6 +79,8 @@
 
 /// Used for obtaining oauth2 access credentials.
 ///
+/// {@macro googleapis_auth_gis_deprecated}
+///
 /// Warning:
 ///
 /// The methods [obtainAccessCredentialsViaUserConsent] and
@@ -72,6 +91,10 @@
 /// methods should only be called inside an event handler, since most
 /// browsers do not block popup windows created in response to a user
 /// interaction.
+@Deprecated(
+  'This member is deprecated. Use requestAccessCredentials or '
+  'requestAuthorizationCode instead.',
+)
 class BrowserOAuth2Flow {
   final ImplicitFlow _flow;
   final RefCountedClient _client;
@@ -241,6 +264,8 @@
 
 /// Represents the result of running a browser based hybrid flow.
 ///
+/// {@macro googleapis_auth_gis_deprecated}
+///
 /// The `credentials` field holds credentials which can be used on the client
 /// side. The `newClient` function can be used to make a new authenticated HTTP
 /// client using these credentials.
@@ -251,6 +276,10 @@
 /// See the `obtainAccessCredentialsViaCodeExchange` function in the
 /// `googleapis_auth.auth_io` library for more details on how to use the
 /// authorization code.
+@Deprecated(
+  'This member is deprecated. Use requestAccessCredentials or '
+  'requestAuthorizationCode instead.',
+)
 class HybridFlowResult {
   final BrowserOAuth2Flow _flow;
 
@@ -277,9 +306,8 @@
   final ImplicitFlow _flow;
   Client _authClient;
 
-  _AutoRefreshingBrowserClient(Client client, this.credentials, this._flow)
-      : _authClient = authenticatedClient(client, credentials),
-        super(client);
+  _AutoRefreshingBrowserClient(super.client, this.credentials, this._flow)
+      : _authClient = authenticatedClient(client, credentials);
 
   @override
   Future<StreamedResponse> send(BaseRequest request) async {
diff --git a/googleapis_auth/lib/auth_io.dart b/googleapis_auth/lib/auth_io.dart
index 63daf15..f99f70e 100644
--- a/googleapis_auth/lib/auth_io.dart
+++ b/googleapis_auth/lib/auth_io.dart
@@ -33,9 +33,9 @@
 ///     in a well-known location (`%APPDATA%/gcloud/application_default_credentials.json`
 ///     on Windows and `$HOME/.config/gcloud/application_default_credentials.json` on Linux/Mac).
 ///  3. On Google Compute Engine and App Engine Flex we fetch credentials from
-///     [GCE metadata service][metadata].
+///     [GCE metadata service][meta-data].
 ///
-/// [metadata]: https://cloud.google.com/compute/docs/storing-retrieving-metadata
+/// [meta-data]: https://cloud.google.com/compute/docs/storing-retrieving-metadata
 /// [svc-keys]: https://cloud.google.com/docs/authentication/getting-started
 /// [gcloud-login]: https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login
 ///
@@ -73,8 +73,12 @@
           .resolve('gcloud/application_default_credentials.json'),
     );
   } else {
+    final homeVar = Platform.environment['HOME'];
+    if (homeVar == null) {
+      throw StateError('The expected environment variable HOME must be set.');
+    }
     credFile = File.fromUri(
-      Uri.directory(Platform.environment['HOME']!)
+      Uri.directory(homeVar)
           .resolve('.config/gcloud/application_default_credentials.json'),
     );
   }
@@ -110,12 +114,14 @@
 ///
 /// {@macro googleapis_auth_close_the_client}
 /// {@macro googleapis_auth_not_close_the_baseClient}
+/// {@macro googleapis_auth_listen_port}
 Future<AutoRefreshingAuthClient> clientViaUserConsent(
   ClientId clientId,
   List<String> scopes,
   PromptUserForConsent userPrompt, {
   Client? baseClient,
   String? hostedDomain,
+  int listenPort = 0,
 }) async {
   var closeUnderlyingClient = false;
   if (baseClient == null) {
@@ -129,6 +135,7 @@
     baseClient,
     userPrompt,
     hostedDomain: hostedDomain,
+    listenPort: listenPort,
   );
 
   AccessCredentials credentials;
@@ -216,12 +223,21 @@
 /// {@macro googleapis_auth_hostedDomain_param}
 ///
 /// {@macro googleapis_auth_user_consent_return}
+///
+/// {@template googleapis_auth_listen_port}
+/// The `localhost` port to use when listening for the redirect from a user
+/// browser interaction. Defaults to `0` - which means the port is dynamic.
+///
+/// Generally you want to specify an explicit port so you can configure it
+/// on the Google Cloud console.
+/// {@endtemplate}
 Future<AccessCredentials> obtainAccessCredentialsViaUserConsent(
   ClientId clientId,
   List<String> scopes,
   Client client,
   PromptUserForConsent userPrompt, {
   String? hostedDomain,
+  int listenPort = 0,
 }) =>
     AuthorizationCodeGrantServerFlow(
       clientId,
@@ -229,6 +245,7 @@
       client,
       userPrompt,
       hostedDomain: hostedDomain,
+      listenPort: listenPort,
     ).run();
 
 /// Obtain oauth2 [AccessCredentials] using the oauth2 authentication code flow.
diff --git a/googleapis_auth/lib/src/auth_functions.dart b/googleapis_auth/lib/src/auth_functions.dart
index e267256..203fc73 100644
--- a/googleapis_auth/lib/src/auth_functions.dart
+++ b/googleapis_auth/lib/src/auth_functions.dart
@@ -46,15 +46,24 @@
 /// [credentials].
 ///
 /// {@macro googleapis_auth_close_the_client}
+///
+/// If [closeUnderlyingClient] is `true`, [AuthClient.close] will also close
+/// [baseClient].
+///
 /// {@macro googleapis_auth_not_close_the_baseClient}
 AuthClient authenticatedClient(
   Client baseClient,
-  AccessCredentials credentials,
-) {
+  AccessCredentials credentials, {
+  bool closeUnderlyingClient = false,
+}) {
   if (credentials.accessToken.type != 'Bearer') {
     throw ArgumentError('Only Bearer access tokens are accepted.');
   }
-  return AuthenticatedClient(baseClient, credentials);
+  return AuthenticatedClient(
+    baseClient,
+    credentials,
+    closeUnderlyingClient: closeUnderlyingClient,
+  );
 }
 
 /// Creates an [AutoRefreshingAuthClient] which automatically refreshes
diff --git a/googleapis_auth/lib/src/auth_http_utils.dart b/googleapis_auth/lib/src/auth_http_utils.dart
index 29a1003..f1798f9 100644
--- a/googleapis_auth/lib/src/auth_http_utils.dart
+++ b/googleapis_auth/lib/src/auth_http_utils.dart
@@ -19,8 +19,12 @@
   final AccessCredentials credentials;
   final String? quotaProject;
 
-  AuthenticatedClient(Client client, this.credentials, {this.quotaProject})
-      : super(client, closeUnderlyingClient: false);
+  AuthenticatedClient(
+    super.client,
+    this.credentials, {
+    this.quotaProject,
+    super.closeUnderlyingClient = false,
+  });
 
   @override
   Future<StreamedResponse> send(BaseRequest request) async {
@@ -54,9 +58,9 @@
 class ApiKeyClient extends DelegatingClient {
   final String _encodedApiKey;
 
-  ApiKeyClient(Client client, String apiKey)
+  ApiKeyClient(super.client, String apiKey)
       : _encodedApiKey = Uri.encodeQueryComponent(apiKey),
-        super(client, closeUnderlyingClient: true);
+        super(closeUnderlyingClient: true);
 
   @override
   Future<StreamedResponse> send(BaseRequest request) async {
@@ -89,14 +93,13 @@
   late Client authClient;
 
   AutoRefreshingClient(
-    Client client,
+    super.client,
     this.clientId,
     this.credentials, {
-    bool closeUnderlyingClient = true,
+    super.closeUnderlyingClient,
     this.quotaProject,
   })  : assert(credentials.accessToken.type == 'Bearer'),
-        assert(credentials.refreshToken != null),
-        super(client, closeUnderlyingClient: closeUnderlyingClient) {
+        assert(credentials.refreshToken != null) {
     authClient = AuthenticatedClient(
       baseClient,
       credentials,
@@ -130,9 +133,9 @@
       StreamController.broadcast(sync: true);
 
   AutoRefreshDelegatingClient(
-    Client client, {
-    bool closeUnderlyingClient = true,
-  }) : super(client, closeUnderlyingClient: closeUnderlyingClient);
+    super.client, {
+    super.closeUnderlyingClient,
+  });
 
   @override
   Stream<AccessCredentials> get credentialUpdates =>
diff --git a/googleapis_auth/lib/src/authentication_exception.dart b/googleapis_auth/lib/src/authentication_exception.dart
new file mode 100644
index 0000000..1973ed2
--- /dev/null
+++ b/googleapis_auth/lib/src/authentication_exception.dart
@@ -0,0 +1,22 @@
+/// Exception thrown when authentication fails.
+class AuthenticationException implements Exception {
+  final String error;
+
+  /// Human-readable ASCII text providing additional information, used to assist
+  /// the client developer in understanding the error that occurred.
+  final String? errorDescription;
+
+  /// A URI identifying a human-readable web page with information about the
+  /// error, used to provide the client developer with additional information
+  /// about the error.
+  final String? errorUri;
+
+  AuthenticationException(
+    this.error, {
+    this.errorDescription,
+    this.errorUri,
+  });
+
+  @override
+  String toString() => 'AuthenticationException: $error';
+}
diff --git a/googleapis_auth/lib/src/browser_utils.dart b/googleapis_auth/lib/src/browser_utils.dart
new file mode 100644
index 0000000..8d749df
--- /dev/null
+++ b/googleapis_auth/lib/src/browser_utils.dart
@@ -0,0 +1,112 @@
+import 'dart:async';
+import 'dart:html' as html;
+import 'dart:js' as js;
+
+import 'authentication_exception.dart';
+
+Future<void> initializeScript(
+  String scriptUrl, {
+  String? onloadParam,
+}) async {
+  final instance = _ScriptLoader._instances.putIfAbsent(
+    scriptUrl,
+    () => _ScriptLoader._(scriptUrl, onloadParam: onloadParam),
+  );
+
+  await instance._initialize();
+}
+
+/// Creates a script that will run properly when strict CSP is enforced.
+///
+/// More specifically, the script has the correct `nonce` value set.
+final html.ScriptElement Function() _createScriptTag = (() {
+  final nonce = _getNonce();
+  if (nonce == null) return html.ScriptElement.new;
+
+  return () => html.ScriptElement()..nonce = nonce;
+})();
+
+/// Returns CSP nonce, if set for any script tag.
+String? _getNonce({html.Window? window}) {
+  final currentWindow = window ?? html.window;
+  final elements = currentWindow.document.querySelectorAll('script');
+  for (final element in elements) {
+    final nonceValue =
+        (element as html.HtmlElement).nonce ?? element.attributes['nonce'];
+    if (nonceValue != null && _noncePattern.hasMatch(nonceValue)) {
+      return nonceValue;
+    }
+  }
+  return null;
+}
+
+// According to the CSP3 spec a nonce must be a valid base64 string.
+// https://w3c.github.io/webappsec-csp/#grammardef-base64-value
+final _noncePattern = RegExp('^[\\w+/_-]+[=]{0,2}\$');
+
+const callbackTimeout = Duration(seconds: 20);
+
+class _ScriptLoader {
+  _ScriptLoader._(
+    this.url, {
+    this.onloadParam,
+  });
+
+  static final _instances = <String, _ScriptLoader>{};
+
+  final String url;
+  final String? onloadParam;
+
+  Future<void>? _pendingInitialization;
+
+  Future<void> _initialize() {
+    if (_pendingInitialization != null) {
+      return _pendingInitialization!;
+    }
+
+    final completer = Completer();
+
+    final timeout = Timer(callbackTimeout, () {
+      _pendingInitialization = null;
+      completer.completeError(
+        AuthenticationException(
+          'Timed out while waiting for library to load: $url',
+        ),
+      );
+    });
+
+    void loadComplete() {
+      timeout.cancel();
+      completer.complete();
+    }
+
+    final loadFunctionName = '_dartScriptLoad_${url.hashCode}';
+
+    js.context[loadFunctionName] = loadComplete;
+    final fullUrl =
+        onloadParam == null ? url : '$url?${onloadParam!}=$loadFunctionName';
+
+    final script = _createScriptTag()
+      ..async = true
+      ..defer = true
+      ..src = fullUrl;
+    if (onloadParam == null) {
+      script.onLoad.first.then((event) {
+        loadComplete();
+      });
+    }
+    script.onError.first.then((errorEvent) {
+      timeout.cancel();
+      _pendingInitialization = null;
+      if (!completer.isCompleted) {
+        // script loading errors can still happen after timeouts
+        completer.completeError(
+            AuthenticationException('Failed to load library: $url'));
+      }
+    });
+    html.document.body!.append(script);
+
+    _pendingInitialization = completer.future;
+    return completer.future;
+  }
+}
diff --git a/googleapis_auth/lib/src/exceptions.dart b/googleapis_auth/lib/src/exceptions.dart
index 59b8a7c..c90c110 100644
--- a/googleapis_auth/lib/src/exceptions.dart
+++ b/googleapis_auth/lib/src/exceptions.dart
@@ -16,7 +16,7 @@
   String toString() => message;
 }
 
-/// Thrown if user did not give his consent.
+/// Thrown if user did not give their consent.
 class UserConsentException implements Exception {
   final String message;
 
diff --git a/googleapis_auth/lib/src/http_client_base.dart b/googleapis_auth/lib/src/http_client_base.dart
index c2c970a..84e37c9 100644
--- a/googleapis_auth/lib/src/http_client_base.dart
+++ b/googleapis_auth/lib/src/http_client_base.dart
@@ -8,8 +8,7 @@
 
 /// Base class for delegating HTTP clients.
 ///
-/// Depending on [closeUnderlyingClient] it will close the client it is
-/// delegating to or not.
+/// If [closeUnderlyingClient] is `true`, [close] will also close [baseClient].
 abstract class DelegatingClient extends BaseClient {
   final Client baseClient;
   final bool closeUnderlyingClient;
@@ -39,9 +38,9 @@
 class RefCountedClient extends DelegatingClient {
   int _refCount;
 
-  RefCountedClient(Client baseClient, {int initialRefCount = 1})
+  RefCountedClient(super.baseClient, {int initialRefCount = 1})
       : _refCount = initialRefCount,
-        super(baseClient, closeUnderlyingClient: true);
+        super(closeUnderlyingClient: true);
 
   @override
   Future<StreamedResponse> send(BaseRequest request) {
@@ -92,9 +91,8 @@
 class RequestImpl extends BaseRequest {
   final Stream<List<int>> _stream;
 
-  RequestImpl(String method, Uri url, [Stream<List<int>>? stream])
-      : _stream = stream ?? const Stream.empty(),
-        super(method, url);
+  RequestImpl(super.method, super.url, [Stream<List<int>>? stream])
+      : _stream = stream ?? const Stream.empty();
 
   @override
   ByteStream finalize() {
diff --git a/googleapis_auth/lib/src/metadata_server_client.dart b/googleapis_auth/lib/src/metadata_server_client.dart
index 302ce55..847a987 100644
--- a/googleapis_auth/lib/src/metadata_server_client.dart
+++ b/googleapis_auth/lib/src/metadata_server_client.dart
@@ -41,6 +41,6 @@
   Client? baseClient,
 }) async =>
     await clientFromFlow(
-      (c) => MetadataServerAuthorizationFlow(c),
+      MetadataServerAuthorizationFlow.new,
       baseClient: baseClient,
     );
diff --git a/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_manual_flow.dart b/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_manual_flow.dart
index b8ee3f1..2613795 100644
--- a/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_manual_flow.dart
+++ b/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_manual_flow.dart
@@ -4,10 +4,7 @@
 
 import 'dart:async';
 
-import 'package:http/http.dart' as http;
-
 import '../access_credentials.dart';
-import '../client_id.dart';
 import '../typedefs.dart';
 import 'auth_code.dart';
 import 'authorization_code_grant_abstract_flow.dart';
@@ -27,12 +24,12 @@
   final PromptUserForConsentManual userPrompt;
 
   AuthorizationCodeGrantManualFlow(
-    ClientId clientId,
-    List<String> scopes,
-    http.Client client,
+    super.clientId,
+    super.scopes,
+    super.client,
     this.userPrompt, {
-    String? hostedDomain,
-  }) : super(clientId, scopes, client, hostedDomain: hostedDomain);
+    super.hostedDomain,
+  });
 
   @override
   Future<AccessCredentials> run() async {
diff --git a/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_server_flow.dart b/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_server_flow.dart
index 95f5b29..52817ac 100644
--- a/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_server_flow.dart
+++ b/googleapis_auth/lib/src/oauth2_flows/authorization_code_grant_server_flow.dart
@@ -5,10 +5,7 @@
 import 'dart:async';
 import 'dart:io';
 
-import 'package:http/http.dart' as http;
-
 import '../access_credentials.dart';
-import '../client_id.dart';
 import '../exceptions.dart';
 import '../typedefs.dart';
 import 'auth_code.dart';
@@ -27,18 +24,20 @@
 class AuthorizationCodeGrantServerFlow
     extends AuthorizationCodeGrantAbstractFlow {
   final PromptUserForConsent userPrompt;
+  final int listenPort;
 
   AuthorizationCodeGrantServerFlow(
-    ClientId clientId,
-    List<String> scopes,
-    http.Client client,
+    super.clientId,
+    super.scopes,
+    super.client,
     this.userPrompt, {
-    String? hostedDomain,
-  }) : super(clientId, scopes, client, hostedDomain: hostedDomain);
+    super.hostedDomain,
+    this.listenPort = 0,
+  });
 
   @override
   Future<AccessCredentials> run() async {
-    final server = await HttpServer.bind('localhost', 0);
+    final server = await HttpServer.bind('localhost', listenPort);
 
     try {
       final port = server.port;
diff --git a/googleapis_auth/lib/src/oauth2_flows/base_flow.dart b/googleapis_auth/lib/src/oauth2_flows/base_flow.dart
index 7408d3b..69aac72 100644
--- a/googleapis_auth/lib/src/oauth2_flows/base_flow.dart
+++ b/googleapis_auth/lib/src/oauth2_flows/base_flow.dart
@@ -39,9 +39,8 @@
   AccessCredentials credentials;
   Client _authClient;
 
-  _FlowClient(Client client, this.credentials, this._flow)
-      : _authClient = authenticatedClient(client, credentials),
-        super(client);
+  _FlowClient(super.client, this.credentials, this._flow)
+      : _authClient = authenticatedClient(client, credentials);
 
   @override
   Future<StreamedResponse> send(BaseRequest request) async {
diff --git a/googleapis_auth/lib/src/oauth2_flows/implicit.dart b/googleapis_auth/lib/src/oauth2_flows/implicit.dart
index 9a93b4d..f538ada 100644
--- a/googleapis_auth/lib/src/oauth2_flows/implicit.dart
+++ b/googleapis_auth/lib/src/oauth2_flows/implicit.dart
@@ -8,16 +8,14 @@
 
 import '../access_credentials.dart';
 import '../access_token.dart';
+import '../authentication_exception.dart';
+import '../browser_utils.dart';
 import '../exceptions.dart';
 import '../response_type.dart';
 
 // This will be overridden by tests.
 String gapiUrl = 'https://apis.google.com/js/client.js';
 
-// According to the CSP3 spec a nonce must be a valid base64 string.
-// https://w3c.github.io/webappsec-csp/#grammardef-base64-value
-final _noncePattern = RegExp('^[\\w+\/_-]+[=]{0,2}\$');
-
 /// This class performs the implicit browser-based oauth2 flow.
 ///
 /// It has to be used in two steps:
@@ -38,61 +36,18 @@
 ///      => Completes with a tuple [AccessCredentials cred, String authCode]
 ///         or an Exception.
 class ImplicitFlow {
-  static const callbackTimeout = Duration(seconds: 20);
-
   final String _clientId;
   final List<String> _scopes;
   final bool _enableDebugLogs;
 
-  /// The pending result of an earlier call to [initialize], if any.
-  ///
-  /// There can be multiple [ImplicitFlow] objects in an application,
-  /// but the gapi JS library should only ever be loaded once. If
-  /// it's called again while a previous initialization is still pending,
-  /// this will be returned.
-  static Future<void>? _pendingInitialization;
-
   ImplicitFlow(this._clientId, this._scopes, this._enableDebugLogs);
 
   /// Readies the flow for calls to [login] by loading the 'gapi'
   /// JavaScript library, or returning the [Future] of a pending
   /// initialization if any object has called this method already.
-  Future<void> initialize() {
-    if (_pendingInitialization != null) {
-      return _pendingInitialization!;
-    }
-
-    final completer = Completer();
-
-    final timeout = Timer(callbackTimeout, () {
-      _pendingInitialization = null;
-      completer.completeError(
-        Exception(
-          'Timed out while waiting for the gapi.auth library to load.',
-        ),
-      );
-    });
-
-    js.context['dartGapiLoaded'] = () {
-      if (_enableDebugLogs) _gapiAuth2.callMethod('enableDebugLogs', [true]);
-      timeout.cancel();
-      completer.complete();
-    };
-
-    final script = _createScript();
-    script.src = '$gapiUrl?onload=dartGapiLoaded';
-    script.onError.first.then((errorEvent) {
-      timeout.cancel();
-      _pendingInitialization = null;
-      if (!completer.isCompleted) {
-        // script loading errors can still happen after timeouts
-        completer.completeError(StateError('Failed to load gapi library.'));
-      }
-    });
-    html.document.body!.append(script);
-
-    _pendingInitialization = completer.future;
-    return completer.future;
+  Future<void> initialize() async {
+    await initializeScript(gapiUrl, onloadParam: 'onload');
+    if (_enableDebugLogs) _gapiAuth2.callMethod('enableDebugLogs', [true]);
   }
 
   Future<LoginResult> loginHybrid({
@@ -184,7 +139,7 @@
     final token = jsTokenObject['access_token'] as String?;
 
     if (token == null || tokenType != 'Bearer') {
-      throw Exception(
+      throw AuthenticationException(
         'Failed to obtain user consent. Invalid server response.',
       );
     }
@@ -193,24 +148,22 @@
 
     if (responseTypes?.contains(ResponseType.idToken) == true &&
         idToken?.isNotEmpty != true) {
-      throw Exception('Expected to get id_token, but did not.');
+      throw AuthenticationException('Expected to get id_token, but did not.');
     }
 
-    List<String>? scopes;
-    final scopeString = jsTokenObject['scope'];
-    if (scopeString is String) {
-      scopes = scopeString.split(' ');
-    }
+    final scopeString = jsTokenObject['scope'] as String;
+    final scopes = scopeString.split(' ');
 
     final expiresAt = jsTokenObject['expires_at'] as int;
     final expiresAtDate =
         DateTime.fromMillisecondsSinceEpoch(expiresAt).toUtc();
 
     final accessToken = AccessToken('Bearer', token, expiresAtDate);
+
     final credentials = AccessCredentials(
       accessToken,
       null,
-      scopes ?? _scopes,
+      scopes,
       idToken: idToken,
     );
 
@@ -219,7 +172,7 @@
       code = jsTokenObject['code'] as String?;
 
       if (code == null) {
-        throw Exception(
+        throw AuthenticationException(
           'Expected to get auth code from server in hybrid flow, but did not.',
         );
       }
@@ -249,29 +202,5 @@
   }
 }
 
-/// Creates a script that will run properly when strict CSP is enforced.
-///
-/// More specifically, the script has the correct `nonce` value set.
-final html.ScriptElement Function() _createScript = (() {
-  final nonce = _getNonce();
-  if (nonce == null) return () => html.ScriptElement();
-
-  return () => html.ScriptElement()..nonce = nonce;
-})();
-
-/// Returns CSP nonce, if set for any script tag.
-String? _getNonce({html.Window? window}) {
-  final currentWindow = window ?? html.window;
-  final elements = currentWindow.document.querySelectorAll('script');
-  for (final element in elements) {
-    final nonceValue =
-        (element as html.HtmlElement).nonce ?? element.attributes['nonce'];
-    if (nonceValue != null && _noncePattern.hasMatch(nonceValue)) {
-      return nonceValue;
-    }
-  }
-  return null;
-}
-
 js.JsObject get _gapiAuth2 =>
     (js.context['gapi'] as js.JsObject)['auth2'] as js.JsObject;
diff --git a/googleapis_auth/lib/src/oauth2_flows/token_model.dart b/googleapis_auth/lib/src/oauth2_flows/token_model.dart
new file mode 100644
index 0000000..0fb2b41
--- /dev/null
+++ b/googleapis_auth/lib/src/oauth2_flows/token_model.dart
@@ -0,0 +1,178 @@
+import 'dart:async';
+import 'dart:html';
+import 'dart:js';
+
+import '../access_credentials.dart';
+import '../access_token.dart';
+import '../authentication_exception.dart';
+import '../browser_utils.dart';
+import '../utils.dart';
+import 'token_model_interop.dart' as interop;
+
+JsObject get _googleAccountsId =>
+    ((context['google'] as JsObject)['accounts'] as JsObject)['id'] as JsObject;
+
+/// Obtains [AccessCredentials] using the
+/// [Google Identity Services](https://developers.google.com/identity/oauth2/web/guides/overview)
+/// token model.
+///
+/// The returned [AccessCredentials] will *always* have a `null` value for
+/// [AccessCredentials.refreshToken] and
+/// [AccessCredentials.idToken].
+///
+/// See
+/// [Choose a user authorization model](https://developers.google.com/identity/oauth2/web/guides/choose-authorization-model)
+/// to understand the tradeoffs between using this function and
+/// [requestAuthorizationCode].
+///
+/// See https://developers.google.com/identity/oauth2/web/guides/use-token-model
+/// and https://developers.google.com/identity/oauth2/web/reference/js-reference
+/// for more details.
+Future<AccessCredentials> requestAccessCredentials({
+  required String clientId,
+  required Iterable<String> scopes,
+  String prompt = 'select_account',
+  @Deprecated('Undocumented feature. Do not include in production code.')
+  String? logLevel,
+}) async {
+  await initializeScript('https://accounts.google.com/gsi/client');
+  if (logLevel != null) _googleAccountsId.callMethod('setLogLevel', [logLevel]);
+
+  final completer = Completer<AccessCredentials>();
+
+  void callback(interop.TokenResponse response) {
+    if (response.error != null) {
+      window.console.log(response);
+      completer.completeError(
+        AuthenticationException(
+          response.error!,
+          errorDescription: response.error_description,
+          errorUri: response.error_uri,
+        ),
+      );
+      return;
+    }
+
+    final token = AccessToken(
+      response.token_type,
+      response.access_token,
+      expiryDate(response.expires_in),
+    );
+
+    final creds = AccessCredentials(token, null, response.scope.split(' '));
+
+    completer.complete(creds);
+  }
+
+  final config = interop.TokenClientConfig(
+    callback: allowInterop(callback),
+    client_id: clientId,
+    scope: scopes.toSet().join(' '),
+    prompt: prompt,
+  );
+
+  final client = interop.initTokenClient(config);
+
+  client.requestAccessToken();
+
+  return completer.future;
+}
+
+/// Obtains [CodeResponse] using the
+/// [Google Identity Services](https://developers.google.com/identity/oauth2/web/guides/overview)
+/// code model.
+///
+/// See
+/// [Choose a user authorization model](https://developers.google.com/identity/oauth2/web/guides/choose-authorization-model)
+/// to understand the tradeoffs between using this function and
+/// [requestAccessCredentials].
+///
+/// See https://developers.google.com/identity/oauth2/web/guides/use-code-model
+/// and https://developers.google.com/identity/oauth2/web/reference/js-reference
+/// for more details.
+Future<CodeResponse> requestAuthorizationCode({
+  required String clientId,
+  required Iterable<String> scopes,
+  String? state,
+  String? hint,
+  String? hostedDomain,
+  @Deprecated('Undocumented feature. Do not include in production code.')
+  String? logLevel,
+}) async {
+  await initializeScript('https://accounts.google.com/gsi/client');
+  if (logLevel != null) _googleAccountsId.callMethod('setLogLevel', [logLevel]);
+
+  final completer = Completer<CodeResponse>();
+
+  void callback(interop.CodeResponse response) {
+    if (response.error != null) {
+      window.console.log(response);
+      completer.completeError(
+        AuthenticationException(
+          response.error!,
+          errorDescription: response.error_description,
+          errorUri: response.error_uri,
+        ),
+      );
+      return;
+    }
+
+    completer.complete(CodeResponse._(
+      code: response.code,
+      scopes: response.scope.split(' '),
+      state: response.state,
+    ));
+  }
+
+  final config = interop.CodeClientConfig(
+    callback: allowInterop(callback),
+    client_id: clientId,
+    scope: scopes.toSet().join(' '),
+    state: state,
+    hint: hint,
+    hosted_domain: hostedDomain,
+  );
+
+  final client = interop.initCodeClient(config);
+
+  client.requestCode();
+
+  return completer.future;
+}
+
+/// Revokes all of the scopes that the user granted to the app.
+///
+/// A valid [accessTokenValue] is required to revoke the permission.
+Future<void> revokeConsent(String accessTokenValue) {
+  final completer = Completer<void>();
+
+  void done(Object? arg) {
+    window.console.log(arg);
+    completer.complete();
+  }
+
+  interop.revoke(accessTokenValue, allowInterop(done));
+
+  return completer.future;
+}
+
+/// Result from a successful call to [requestAuthorizationCode].
+///
+/// See https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse
+/// for more details.
+class CodeResponse {
+  CodeResponse._({required this.code, required this.scopes, this.state});
+
+  /// The authorization code of a successful token response.
+  final String code;
+
+  /// The list of scopes that are approved by the user.
+  final List<String> scopes;
+
+  /// The string value that your application uses to maintain state between your
+  /// authorization request and the response.
+  final String? state;
+
+  @override
+  String toString() => 'CodeResponse: $code';
+}
diff --git a/googleapis_auth/lib/src/oauth2_flows/token_model_interop.dart b/googleapis_auth/lib/src/oauth2_flows/token_model_interop.dart
new file mode 100644
index 0000000..7a1a8fe
--- /dev/null
+++ b/googleapis_auth/lib/src/oauth2_flows/token_model_interop.dart
@@ -0,0 +1,115 @@
+// ignore_for_file: non_constant_identifier_names
+
+@JS('google.accounts.oauth2')
+library token_model_interop;
+
+import 'package:js/js.dart';
+
+@JS()
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#google.accounts.oauth2.initTokenClient
+external TokenClient initTokenClient(TokenClientConfig config);
+
+@JS()
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#google.accounts.oauth2.revoke
+external void revoke(String accessToken, [void Function(Object?) done]);
+
+@JS()
+@anonymous
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#TokenClientConfig
+class TokenClientConfig {
+  external factory TokenClientConfig({
+    required String client_id,
+    required String scope,
+    required void Function(TokenResponse) callback,
+    String hint,
+    String hosted_domain,
+    String prompt,
+  });
+
+  // state: not recommended
+  // enable_serial_consent: skipping. only for old clients
+}
+
+@JS()
+@anonymous
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#TokenResponse
+class TokenResponse {
+  external String get access_token;
+  external int get expires_in;
+  external String get hd;
+  external String get prompt;
+  external String get token_type;
+  external String get scope;
+
+  /// A single ASCII error code.
+  external String? get error;
+
+  /// Human-readable ASCII text providing additional information, used to assist
+  /// the client developer in understanding the error that occurred.
+  external String? get error_description;
+
+  /// A URI identifying a human-readable web page with information about the
+  /// error, used to provide the client developer with additional information
+  /// about the error.
+  external String? get error_uri;
+}
+
+@JS()
+@anonymous
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#google.accounts.oauth2.initTokenClient
+class TokenClient {
+  external void requestAccessToken();
+}
+
+@JS()
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#google.accounts.oauth2.initCodeClient
+external CodeClient initCodeClient(CodeClientConfig config);
+
+@JS()
+@anonymous
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#CodeClient
+class CodeClient {
+  external void requestCode();
+}
+
+@JS()
+@anonymous
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#CodeClientConfig
+class CodeClientConfig {
+  external factory CodeClientConfig({
+    required String client_id,
+    required String scope,
+    String? redirect_uri,
+    required void Function(CodeResponse) callback,
+    String? state,
+    String? hint,
+    String? hosted_domain,
+    String? ux_mode,
+    bool select_account,
+  });
+}
+
+@JS()
+@anonymous
+// https://developers.google.com/identity/oauth2/web/reference/js-reference?hl=en#CodeResponse
+class CodeResponse {
+  external String get code;
+  external String get scope;
+  external String? get state;
+
+  external String get authuser;
+  external String get hd;
+  external String get prompt;
+
+  /// A single ASCII error code.
+  external String? get error;
+
+  /// Human-readable ASCII text providing additional information, used to assist
+  /// the client developer in understanding the error that occurred.
+  external String? get error_description;
+
+  /// A URI identifying a human-readable web page with information about the
+  /// error, used to provide the client developer with additional information
+  /// about the error.
+  external String? get error_uri;
+}
diff --git a/googleapis_auth/lib/src/service_account_credentials.dart b/googleapis_auth/lib/src/service_account_credentials.dart
index 0646838..9c02e22 100644
--- a/googleapis_auth/lib/src/service_account_credentials.dart
+++ b/googleapis_auth/lib/src/service_account_credentials.dart
@@ -38,7 +38,8 @@
   ///
   /// The optional named argument [impersonatedUser] is used to set the user
   /// to impersonate if impersonating a user.
-  factory ServiceAccountCredentials.fromJson(json, {String? impersonatedUser}) {
+  factory ServiceAccountCredentials.fromJson(Object? json,
+      {String? impersonatedUser}) {
     if (json is String) {
       json = jsonDecode(json);
     }
diff --git a/googleapis_auth/lib/src/typedefs.dart b/googleapis_auth/lib/src/typedefs.dart
index 0010862..3228e11 100644
--- a/googleapis_auth/lib/src/typedefs.dart
+++ b/googleapis_auth/lib/src/typedefs.dart
@@ -5,13 +5,13 @@
 /// Function for directing the user or it's user-agent to [uri].
 ///
 /// The user is required to go to [uri] and either approve or decline the
-/// application's request for access resources on his behalf.
+/// application's request for access resources on their behalf.
 typedef PromptUserForConsent = void Function(String uri);
 
 /// Function for directing the user or it's user-agent to [uri].
 ///
 /// The user is required to go to [uri] and either approve or decline the
-/// application's request for access resources on his behalf.
+/// application's request for access resources on their behalf.
 ///
 /// The user will be given an authorization code. This function should complete
 /// with this authorization code. If the user declined to give access this
diff --git a/googleapis_auth/mono_pkg.yaml b/googleapis_auth/mono_pkg.yaml
index 00f4fe5..444b620 100644
--- a/googleapis_auth/mono_pkg.yaml
+++ b/googleapis_auth/mono_pkg.yaml
@@ -1,6 +1,6 @@
 # See https://pub.dev/packages/mono_repo
 sdk:
-- 2.13.0
+- pubspec
 - dev
 
 stages:
@@ -8,6 +8,10 @@
   - group:
     - format
     - analyze: --fatal-infos .
+    sdk: dev
+  - group:
+    - analyze
+    sdk: pubspec
 - unittest:
   - test: -p vm
   - test: -p chrome
diff --git a/googleapis_auth/pubspec.yaml b/googleapis_auth/pubspec.yaml
index 63e9eea..db58cbf 100644
--- a/googleapis_auth/pubspec.yaml
+++ b/googleapis_auth/pubspec.yaml
@@ -1,22 +1,25 @@
 name: googleapis_auth
-version: 1.3.1
+version: 1.4.1
 description: Obtain Access credentials for Google services using OAuth 2.0
 repository: https://github.com/google/googleapis.dart/tree/master/googleapis_auth
 
 environment:
-  sdk: '>=2.13.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
+  args: ^2.3.1
   crypto: ^3.0.0
-  http: ^0.13.0
+  http: '>=0.13.5 <2.0.0'
   http_parser: ^4.0.0
+  js: ^0.6.4
 
 dev_dependencies:
   # build_ dependencies allow debugging web tests using:
   #   `dart pub run build_runner serve test`
   build_runner: ^2.0.0
   build_test: ^2.0.0
-  build_web_compilers: ^3.0.0
+  build_web_compilers: '>=3.2.7 <5.0.0'
+  dart_flutter_team_lints: ^1.0.0
   test: ^1.16.0
 
 false_secrets:
diff --git a/html/BUILD.gn b/html/BUILD.gn
index 95576ce..480d714 100644
--- a/html/BUILD.gn
+++ b/html/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for html-0.15.1
+# This file is generated by package_importer.py for html-0.15.2
 
 import("//build/dart/dart_library.gni")
 
 dart_library("html") {
   package_name = "html"
 
-  language_version = "2.12"
+  language_version = "2.17"
 
   disable_analysis = true
 
diff --git a/html/CHANGELOG.md b/html/CHANGELOG.md
index ae8d726..725d157 100644
--- a/html/CHANGELOG.md
+++ b/html/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.15.2
+
+- Add additional types at the API boundary (in `lib/parser.dart` and others).
+- Adopted the `package:dart_flutter_team_lints` linting rules.
+- Fixed an issue with `querySelector` where it would fail in some cases with
+  descendant or sibling combinators (#157).
+- Add an API example in `example/`.
+
 ## 0.15.1
 
 - Move `htmlSerializeEscape` to its own library,
diff --git a/html/README.md b/html/README.md
index f5388b2..05a19a8 100644
--- a/html/README.md
+++ b/html/README.md
@@ -9,9 +9,9 @@
 Parsing HTML is easy!
 
 ```dart
-import 'package:html/parser.dart' show parse;
+import 'package:html/parser.dart';
 
-main() {
+void main() {
   var document = parse(
       '<body>Hello world! <a href="www.html5rocks.com">HTML5 rocks!');
   print(document.outerHtml);
diff --git a/html/analysis_options.yaml b/html/analysis_options.yaml
index 5f87ffe..90d920e 100644
--- a/html/analysis_options.yaml
+++ b/html/analysis_options.yaml
@@ -1,48 +1,10 @@
-include: package:lints/recommended.yaml
+include: package:dart_flutter_team_lints/analysis_options.yaml
 
 analyzer:
   language:
     strict-casts: true
+    strict-raw-types: true
   errors:
+    lines_longer_than_80_chars: ignore
     # https://github.com/dart-lang/linter/issues/1649
     prefer_collection_literals: ignore
-
-linter:
-  rules:
-    - avoid_dynamic_calls
-    - avoid_function_literals_in_foreach_calls
-    - avoid_returning_null
-    - avoid_unused_constructor_parameters
-    - await_only_futures
-    - camel_case_types
-    - cancel_subscriptions
-    - comment_references
-    # See https://github.com/dart-lang/logging/issues/43
-    #- constant_identifier_names
-    - control_flow_in_finally
-    - directives_ordering
-    - empty_statements
-    - hash_and_equals
-    - implementation_imports
-    #- invariant_booleans
-    - iterable_contains_unrelated_type
-    - list_remove_unrelated_type
-    - no_adjacent_strings_in_list
-    - non_constant_identifier_names
-    - only_throw_errors
-    - overridden_fields
-    - package_api_docs
-    - package_names
-    - package_prefixed_library_names
-    - prefer_const_constructors
-    - prefer_final_locals
-    - prefer_initializing_formals
-    - prefer_interpolation_to_compose_strings
-    - prefer_typing_uninitialized_variables
-    - test_types_in_equals
-    - throw_in_finally
-    - unnecessary_brace_in_string_interps
-    - unnecessary_getters_setters
-    - unnecessary_lambdas
-    - unnecessary_null_aware_assignments
-    - unnecessary_statements
diff --git a/html/example/main.dart b/html/example/main.dart
new file mode 100644
index 0000000..309ed7c
--- /dev/null
+++ b/html/example/main.dart
@@ -0,0 +1,57 @@
+import 'package:html/dom.dart';
+import 'package:html/dom_parsing.dart';
+import 'package:html/parser.dart';
+
+void main(List<String> args) {
+  var document = parse('''
+<body>
+  <h2>Header 1</h2>
+  <p>Text.</p>
+  <h2>Header 2</h2>
+  More text.
+  <br/>
+</body>''');
+
+  // outerHtml output
+  print('outer html:');
+  print(document.outerHtml);
+
+  print('');
+
+  // visitor output
+  print('html visitor:');
+  _Visitor().visit(document);
+}
+
+// Note: this example visitor doesn't handle things like printing attributes and
+// such.
+class _Visitor extends TreeVisitor {
+  String indent = '';
+
+  @override
+  void visitText(Text node) {
+    if (node.data.trim().isNotEmpty) {
+      print('$indent${node.data.trim()}');
+    }
+  }
+
+  @override
+  void visitElement(Element node) {
+    if (isVoidElement(node.localName)) {
+      print('$indent<${node.localName}/>');
+    } else {
+      print('$indent<${node.localName}>');
+      indent += '  ';
+      visitChildren(node);
+      indent = indent.substring(0, indent.length - 2);
+      print('$indent</${node.localName}>');
+    }
+  }
+
+  @override
+  void visitChildren(Node node) {
+    for (var child in node.nodes) {
+      visit(child);
+    }
+  }
+}
diff --git a/html/lib/dom.dart b/html/lib/dom.dart
index 5ffc15f..28d7594 100644
--- a/html/lib/dom.dart
+++ b/html/lib/dom.dart
@@ -60,7 +60,7 @@
   int compareTo(Object other) {
     // Not sure about this sort order
     if (other is! AttributeName) return 1;
-    var cmp = (prefix ?? '').compareTo((other.prefix ?? ''));
+    var cmp = (prefix ?? '').compareTo(other.prefix ?? '');
     if (cmp != 0) return cmp;
     cmp = name.compareTo(other.name);
     if (cmp != 0) return cmp;
diff --git a/html/lib/parser.dart b/html/lib/parser.dart
index 30c34b3..3eb7a97 100644
--- a/html/lib/parser.dart
+++ b/html/lib/parser.dart
@@ -15,6 +15,7 @@
 
 import 'dart:collection';
 import 'dart:math';
+
 import 'package:source_span/source_span.dart';
 
 import 'dom.dart';
@@ -36,7 +37,7 @@
 /// [Node.sourceSpan] property will be `null`. When using [generateSpans] you
 /// can additionally pass [sourceUrl] to indicate where the [input] was
 /// extracted from.
-Document parse(input,
+Document parse(dynamic input,
     {String? encoding, bool generateSpans = false, String? sourceUrl}) {
   final p = HtmlParser(input,
       encoding: encoding, generateSpans: generateSpans, sourceUrl: sourceUrl);
@@ -55,7 +56,7 @@
 /// [Node.sourceSpan] property will be `null`. When using [generateSpans] you can
 /// additionally pass [sourceUrl] to indicate where the [input] was extracted
 /// from.
-DocumentFragment parseFragment(input,
+DocumentFragment parseFragment(dynamic input,
     {String container = 'div',
     String? encoding,
     bool generateSpans = false,
@@ -93,7 +94,7 @@
 
   Phase? originalPhase;
 
-  var framesetOK = true;
+  bool framesetOK = true;
 
   // These fields hold the different phase singletons. At any given time one
   // of them will be active.
@@ -140,7 +141,7 @@
   /// automatic conversion of element and attribute names to lower case. Note
   /// that standard way to parse HTML is to lowercase, which is what the browser
   /// DOM will do if you request `Element.outerHTML`, for example.
-  HtmlParser(input,
+  HtmlParser(dynamic input,
       {String? encoding,
       bool parseMeta = true,
       bool lowercaseElementName = true,
@@ -150,7 +151,7 @@
       String? sourceUrl,
       TreeBuilder? tree})
       : tree = tree ?? TreeBuilder(true),
-        tokenizer = (input is HtmlTokenizer
+        tokenizer = input is HtmlTokenizer
             ? input
             : HtmlTokenizer(input,
                 encoding: encoding,
@@ -158,7 +159,7 @@
                 lowercaseElementName: lowercaseElementName,
                 lowercaseAttrName: lowercaseAttrName,
                 generateSpans: generateSpans,
-                sourceUrl: sourceUrl)) {
+                sourceUrl: sourceUrl) {
     tokenizer.parser = this;
   }
 
@@ -348,7 +349,7 @@
       .pointSpan();
 
   void parseError(SourceSpan? span, String errorcode,
-      [Map? datavars = const {}]) {
+      [Map<String, Object?>? datavars = const {}]) {
     if (!generateSpans && span == null) {
       span = _lastSpan;
     }
@@ -652,9 +653,9 @@
     final systemId = token.systemId;
     final correct = token.correct;
 
-    if ((name != 'html' ||
+    if (name != 'html' ||
         publicId != null ||
-        systemId != null && systemId != 'about:legacy-compat')) {
+        systemId != null && systemId != 'about:legacy-compat') {
       parser.parseError(token.span, 'unknown-doctype');
     }
 
@@ -1572,8 +1573,8 @@
 
   void startTagFrameset(StartTagToken token) {
     parser.parseError(token.span, 'unexpected-start-tag', {'name': 'frameset'});
-    if ((tree.openElements.length == 1 ||
-        tree.openElements[1].localName != 'body')) {
+    if (tree.openElements.length == 1 ||
+        tree.openElements[1].localName != 'body') {
       assert(parser.innerHTMLMode);
     } else if (parser.framesetOK) {
       if (tree.openElements[1].parentNode != null) {
@@ -2120,7 +2121,7 @@
         }
         // Step 6.4
         if (lastNode == furthestBlock) {
-          bookmark = (tree.activeFormattingElements.indexOf(node) + 1);
+          bookmark = tree.activeFormattingElements.indexOf(node) + 1;
         }
         // Step 6.5
         //cite = node.parent
@@ -3943,7 +3944,7 @@
   final String errorCode;
   @override
   final SourceSpan? span;
-  final Map? data;
+  final Map<String, Object?>? data;
 
   ParseError(this.errorCode, this.span, this.data);
 
@@ -3959,7 +3960,7 @@
   String get message => formatStr(errorMessages[errorCode]!, data);
 
   @override
-  String toString({color}) {
+  String toString({dynamic color}) {
     final res = span!.message(message, color: color);
     return span!.sourceUrl == null ? 'ParserError on $res' : 'On $res';
   }
diff --git a/html/lib/src/constants.dart b/html/lib/src/constants.dart
index e32e037..5c41f81 100644
--- a/html/lib/src/constants.dart
+++ b/html/lib/src/constants.dart
@@ -200,7 +200,7 @@
   'unexpected-end-tag-after-body': 'Unexpected end tag token (%(name)s)'
       ' in the after body phase.',
   'unexpected-char-in-frameset':
-      'Unepxected characters in the frameset phase. Characters ignored.',
+      'Unexpected characters in the frameset phase. Characters ignored.',
   'unexpected-start-tag-in-frameset': 'Unexpected start tag token (%(name)s)'
       ' in the frameset phase. Ignored.',
   'unexpected-frameset-in-frameset-innerhtml':
@@ -265,7 +265,7 @@
   }
 }
 
-const List scopingElements = [
+const List<Pair<String, String>> scopingElements = [
   Pair(Namespaces.html, 'applet'),
   Pair(Namespaces.html, 'caption'),
   Pair(Namespaces.html, 'html'),
diff --git a/html/lib/src/encoding_parser.dart b/html/lib/src/encoding_parser.dart
index defe57e..2ecd5c4 100644
--- a/html/lib/src/encoding_parser.dart
+++ b/html/lib/src/encoding_parser.dart
@@ -1,9 +1,8 @@
 import 'constants.dart';
 import 'html_input_stream.dart';
 
-// TODO(jmesserly): I converted StopIteration to StateError("No more elements").
-// Seems strange to throw this from outside of an iterator though.
-/// String-like object with an associated position and various extra methods
+/// String-like object with an associated position and various extra methods.
+///
 /// If the position is ever greater than the string length then an exception is
 /// raised.
 class EncodingBytes {
@@ -17,7 +16,7 @@
   String _next() {
     final p = __position = __position + 1;
     if (p >= _length) {
-      throw StateError('No more elements');
+      throw _EncodingRangeException('No more elements');
     } else if (p < 0) {
       throw RangeError(p);
     }
@@ -27,7 +26,7 @@
   String _previous() {
     var p = __position;
     if (p >= _length) {
-      throw StateError('No more elements');
+      throw _EncodingRangeException('No more elements');
     } else if (p < 0) {
       throw RangeError(p);
     }
@@ -37,14 +36,14 @@
 
   set _position(int value) {
     if (__position >= _length) {
-      throw StateError('No more elements');
+      throw _EncodingRangeException('No more elements');
     }
     __position = value;
   }
 
   int get _position {
     if (__position >= _length) {
-      throw StateError('No more elements');
+      throw _EncodingRangeException('No more elements');
     }
     if (__position >= 0) {
       return __position;
@@ -108,7 +107,7 @@
       __position = newPosition + bytes.length - 1;
       return true;
     } else {
-      throw StateError('No more elements');
+      throw _EncodingRangeException('No more elements');
     }
   }
 
@@ -161,7 +160,7 @@
         }
         _data._position += 1;
       }
-    } on StateError catch (_) {
+    } on _EncodingRangeException catch (_) {
       // Catch this here to match behavior of Python's StopIteration
       // TODO(jmesserly): refactor to not use exceptions
     }
@@ -355,12 +354,12 @@
         try {
           data._skipUntil(isWhitespace);
           return data._slice(oldPosition, data._position);
-        } on StateError catch (_) {
+        } on _EncodingRangeException catch (_) {
           //Return the whole remaining value
           return data._slice(oldPosition);
         }
       }
-    } on StateError catch (_) {
+    } on _EncodingRangeException catch (_) {
       return null;
     }
   }
@@ -371,3 +370,9 @@
 }
 
 typedef _CharPredicate = bool Function(String char);
+
+class _EncodingRangeException implements Exception {
+  final String message;
+
+  _EncodingRangeException(this.message);
+}
diff --git a/html/lib/src/html_input_stream.dart b/html/lib/src/html_input_stream.dart
index a0e490f..1c1bcc8 100644
--- a/html/lib/src/html_input_stream.dart
+++ b/html/lib/src/html_input_stream.dart
@@ -22,7 +22,7 @@
   /// The name of the character encoding.
   String? charEncodingName;
 
-  /// True if we are certain about [charEncodingName], false for tenative.
+  /// True if we are certain about [charEncodingName], false for tentative.
   bool charEncodingCertain = true;
 
   final bool generateSpans;
@@ -35,7 +35,7 @@
   /// Raw UTF-16 codes, used if a Dart String is passed in.
   List<int>? _rawChars;
 
-  var errors = Queue<String>();
+  Queue<String> errors = Queue<String>();
 
   SourceFile? fileInfo;
 
@@ -57,7 +57,7 @@
   /// element)
   ///
   /// [parseMeta] - Look for a <meta> element containing encoding information
-  HtmlInputStream(source,
+  HtmlInputStream(dynamic source,
       [String? encoding,
       bool parseMeta = true,
       this.generateSpans = false,
diff --git a/html/lib/src/list_proxy.dart b/html/lib/src/list_proxy.dart
index 05086fa..7fe5e82 100644
--- a/html/lib/src/list_proxy.dart
+++ b/html/lib/src/list_proxy.dart
@@ -22,7 +22,7 @@
   E operator [](int index) => _list[index];
 
   @override
-  operator []=(int index, E value) {
+  void operator []=(int index, E value) {
     _list[index] = value;
   }
 
diff --git a/html/lib/src/query_selector.dart b/html/lib/src/query_selector.dart
index b596745..c45d11c 100644
--- a/html/lib/src/query_selector.dart
+++ b/html/lib/src/query_selector.dart
@@ -1,9 +1,8 @@
 /// Query selector implementation for our DOM.
 library html.src.query;
 
-import 'package:csslib/parser.dart' as css;
-import 'package:csslib/parser.dart' show TokenKind, Message;
-import 'package:csslib/visitor.dart'; // the CSSOM
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
 import 'package:html/dom.dart';
 import 'package:html/src/constants.dart' show isWhitespaceCC;
 
@@ -23,7 +22,7 @@
 // http://dev.w3.org/csswg/selectors-4/#grouping
 SelectorGroup _parseSelectorList(String selector) {
   final errors = <Message>[];
-  final group = css.parseSelectorGroup(selector, errors: errors);
+  final group = parseSelectorGroup(selector, errors: errors);
   if (group == null || errors.isNotEmpty) {
     throw FormatException("'$selector' is not a valid selector: $errors");
   }
@@ -70,22 +69,25 @@
     for (var s in node.simpleSelectorSequences.reversed) {
       if (combinator == null) {
         result = s.simpleSelector.visit(this) as bool;
-      } else if (combinator == TokenKind.COMBINATOR_DESCENDANT) {
-        // descendant combinator
-        // http://dev.w3.org/csswg/selectors-4/#descendant-combinators
-        do {
-          _element = _element!.parent;
-        } while (_element != null && !(s.simpleSelector.visit(this) as bool));
+      } else {
+        if (combinator == TokenKind.COMBINATOR_DESCENDANT) {
+          // descendant combinator
+          // http://dev.w3.org/csswg/selectors-4/#descendant-combinators
+          do {
+            _element = _element!.parent;
+          } while (_element != null && !(s.simpleSelector.visit(this) as bool));
 
-        if (_element == null) result = false;
-      } else if (combinator == TokenKind.COMBINATOR_TILDE) {
-        // Following-sibling combinator
-        // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators
-        do {
-          _element = _element!.previousElementSibling;
-        } while (_element != null && !(s.simpleSelector.visit(this) as bool));
+          if (_element == null) result = false;
+        } else if (combinator == TokenKind.COMBINATOR_TILDE) {
+          // Following-sibling combinator
+          // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators
+          do {
+            _element = _element!.previousElementSibling;
+          } while (_element != null && !(s.simpleSelector.visit(this) as bool));
 
-        if (_element == null) result = false;
+          if (_element == null) result = false;
+        }
+        combinator = null;
       }
 
       if (!result) break;
@@ -127,7 +129,7 @@
       UnimplementedError("'$selector' selector of type "
           '${selector.runtimeType} is not implemented');
 
-  FormatException _unsupported(selector) =>
+  FormatException _unsupported(TreeNode selector) =>
       FormatException("'$selector' is not a valid selector");
 
   @override
diff --git a/html/lib/src/token.dart b/html/lib/src/token.dart
index b2c3f40..7a4e743 100644
--- a/html/lib/src/token.dart
+++ b/html/lib/src/token.dart
@@ -2,6 +2,7 @@
 library token;
 
 import 'dart:collection';
+
 import 'package:source_span/source_span.dart';
 
 /// An html5 token.
@@ -74,7 +75,7 @@
 
 class ParseErrorToken extends StringToken {
   /// Extra information that goes along with the error message.
-  Map? messageParams;
+  Map<String, Object?>? messageParams;
 
   ParseErrorToken(String data, {this.messageParams}) : super(data);
 
diff --git a/html/lib/src/tokenizer.dart b/html/lib/src/tokenizer.dart
index 603d405..1ab72a9 100644
--- a/html/lib/src/tokenizer.dart
+++ b/html/lib/src/tokenizer.dart
@@ -1,7 +1,9 @@
 library tokenizer;
 
 import 'dart:collection';
+
 import 'package:html/parser.dart' show HtmlParser;
+
 import 'constants.dart';
 import 'html_input_stream.dart';
 import 'token.dart';
@@ -63,7 +65,7 @@
   List<TagAttribute>? _attributes;
   Set<String>? _attributeNames;
 
-  HtmlTokenizer(doc,
+  HtmlTokenizer(dynamic doc,
       {String? encoding,
       bool parseMeta = true,
       this.lowercaseElementName = true,
diff --git a/html/lib/src/treebuilder.dart b/html/lib/src/treebuilder.dart
index d477eb0..3e39700 100644
--- a/html/lib/src/treebuilder.dart
+++ b/html/lib/src/treebuilder.dart
@@ -2,9 +2,11 @@
 library treebuilder;
 
 import 'dart:collection';
+
 import 'package:html/dom.dart';
 import 'package:html/parser.dart' show getElementNameTuple;
 import 'package:source_span/source_span.dart';
+
 import 'constants.dart';
 import 'list_proxy.dart';
 import 'token.dart';
@@ -47,7 +49,7 @@
 }
 
 // TODO(jmesserly): this should exist in corelib...
-bool _mapEquals(Map a, Map b) {
+bool _mapEquals(Map<Object, String> a, Map<Object, String> b) {
   if (a.length != b.length) return false;
   if (a.isEmpty) return true;
 
@@ -85,7 +87,7 @@
 
   /// Switch the function used to insert an element from the
   /// normal one to the misnested table one and back again
-  var insertFromTable = false;
+  bool insertFromTable = false;
 
   TreeBuilder(bool namespaceHTMLElements)
       : defaultNamespace = namespaceHTMLElements ? Namespaces.html : null {
@@ -105,13 +107,13 @@
     document = Document();
   }
 
-  bool elementInScope(target, {String? variant}) {
-    //If we pass a node in we match that. if we pass a string
-    //match any node with that name
+  bool elementInScope(dynamic target, {String? variant}) {
+    // If we pass a node in we match that. If we pass a string match any node
+    // with that name.
     final exactNode = target is Node;
 
     var listElements1 = scopingElements;
-    var listElements2 = const <Pair>[];
+    var listElements2 = const <Pair<String, String>>[];
     var invert = false;
     if (variant != null) {
       switch (variant) {
diff --git a/html/lib/src/utils.dart b/html/lib/src/utils.dart
index 001c706..5c4e359 100644
--- a/html/lib/src/utils.dart
+++ b/html/lib/src/utils.dart
@@ -51,7 +51,7 @@
 /// Format a string like Python's % string format operator. Right now this only
 /// supports a [data] dictionary used with %s or %08x. Those were the only
 /// things needed for [errorMessages].
-String formatStr(String format, Map? data) {
+String formatStr(String format, Map<String, Object?>? data) {
   if (data == null) return format;
   data.forEach((key, value) {
     final result = StringBuffer();
diff --git a/html/pubspec.yaml b/html/pubspec.yaml
index 7b9527c..eb222a6 100644
--- a/html/pubspec.yaml
+++ b/html/pubspec.yaml
@@ -1,16 +1,16 @@
 name: html
-version: 0.15.1
+version: 0.15.2
 description: APIs for parsing and manipulating HTML content outside the browser.
 repository: https://github.com/dart-lang/html
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: '>=2.17.0 <3.0.0'
 
 dependencies:
   csslib: ^0.17.0
   source_span: ^1.8.0
 
 dev_dependencies:
-  lints: ^1.0.0
+  dart_flutter_team_lints: ^0.1.0
   path: ^1.8.0
   test: ^1.16.0
diff --git a/json_annotation/BUILD.gn b/json_annotation/BUILD.gn
index a9cd21a..3380e25 100644
--- a/json_annotation/BUILD.gn
+++ b/json_annotation/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for json_annotation-4.8.0
+# This file is generated by package_importer.py for json_annotation-4.8.1
 
 import("//build/dart/dart_library.gni")
 
 dart_library("json_annotation") {
   package_name = "json_annotation"
 
-  language_version = "2.18"
+  language_version = "2.19"
 
   disable_analysis = true
 
diff --git a/json_annotation/CHANGELOG.md b/json_annotation/CHANGELOG.md
index d784dde..ee6b48c 100644
--- a/json_annotation/CHANGELOG.md
+++ b/json_annotation/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 4.8.1
+
+- Require Dart 2.19
+- Add topics.
+
 ## 4.8.0
 
 - DEPRECATED `JsonKey.ignore`. Replaced by...
diff --git a/json_annotation/lib/src/checked_helpers.dart b/json_annotation/lib/src/checked_helpers.dart
index e3da209..04c54d8 100644
--- a/json_annotation/lib/src/checked_helpers.dart
+++ b/json_annotation/lib/src/checked_helpers.dart
@@ -17,8 +17,7 @@
       S Function(Object?), {
       Object? Function(Map, String)? readValue,
     }),
-  )
-      constructor, {
+  ) constructor, {
   Map<String, String> fieldKeyMap = const {},
 }) {
   Q checkedConvert<Q>(
diff --git a/json_annotation/lib/src/json_serializable.dart b/json_annotation/lib/src/json_serializable.dart
index 41fa0c1..71a6e45 100644
--- a/json_annotation/lib/src/json_serializable.dart
+++ b/json_annotation/lib/src/json_serializable.dart
@@ -88,7 +88,7 @@
   final bool? createFieldMap;
 
   /// If `true` (defaults to false), a private, static `_$ExamplePerFieldToJson`
-  /// abstract class will be geenrated in the part file.
+  /// abstract class will be generated in the part file.
   ///
   /// This abstract class will contain one static function per property,
   /// exposing a way to encode only this property instead of the entire object.
diff --git a/json_annotation/mono_pkg.yaml b/json_annotation/mono_pkg.yaml
index 9712620..305682a 100644
--- a/json_annotation/mono_pkg.yaml
+++ b/json_annotation/mono_pkg.yaml
@@ -1,10 +1,10 @@
 # See https://github.com/google/mono_repo.dart for details on this file
-sdk:
-- pubspec
-- dev
-
 stages:
 - analyzer_and_format:
   - group:
     - format
     - analyze: --fatal-infos .
+    sdk: dev
+  - group:
+    - analyze
+    sdk: pubspec
diff --git a/json_annotation/pubspec.yaml b/json_annotation/pubspec.yaml
index 68397ae..64bc29b 100644
--- a/json_annotation/pubspec.yaml
+++ b/json_annotation/pubspec.yaml
@@ -1,18 +1,23 @@
 name: json_annotation
-version: 4.8.0
+version: 4.8.1
 description: >-
   Classes and helper functions that support JSON code generation via the
   `json_serializable` package.
 repository: https://github.com/google/json_serializable.dart/tree/master/json_annotation
+topics:
+ - json
+ - build-runner
+ - json-serializable
+ - codegen
 
 environment:
-  sdk: '>=2.18.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
   meta: ^1.4.0
 
 dev_dependencies:
-  lints: ^2.0.0
+  dart_flutter_team_lints: ^1.0.0
 # When changing JsonSerializable class.
 #  build_runner: ^2.0.0
 #  json_serializable: any
diff --git a/meta/BUILD.gn b/meta/BUILD.gn
index 8d41990..af41259 100644
--- a/meta/BUILD.gn
+++ b/meta/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for meta-1.9.0
+# This file is generated by package_importer.py for meta-1.9.1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/meta/CHANGELOG.md b/meta/CHANGELOG.md
index 9efa6a9..e8aafa2 100644
--- a/meta/CHANGELOG.md
+++ b/meta/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.9.1
+
+* Update SDK constraints to `>=2.12.0 <4.0.0`.
+* Mark `@reopen` stable.
+
 ## 1.9.0
 
 * Introduce `@reopen` to annotate class or mixin declarations that can safely
diff --git a/meta/lib/meta.dart b/meta/lib/meta.dart
index 93dc857..85ad4eb 100644
--- a/meta/lib/meta.dart
+++ b/meta/lib/meta.dart
@@ -268,42 +268,35 @@
 // "referenced."
 const _Protected protected = _Protected();
 
-/// Annotation for intentionally loosening restrictions on subtyping.
+// todo(pq): add a link to `implicit_reopen` once implemented.
+
+/// Annotation for intentionally loosening restrictions on subtyping that would
+/// otherwise cause lint warnings to be produced by the `implicit_reopen` lint.
 ///
-/// Indicates that the annotated class or mixin declaration
-/// intentionally allows subclasses to implement or extend it, even
-/// though it has a superclass which does not allow that.
+/// Indicates that the annotated class, mixin, or mixin class declaration
+/// intentionally allows subtypes outside the library to implement it, or extend
+/// it, or mix it in, even though it has some superinterfaces whose restrictions
+/// prevent inheritance.
 ///
-/// A declaration annotated with `@reopen` will not generate warnings from the
-/// `implicit_reopen` lint. That lint will otherwise warn when a subclass *C*
-/// removes some of the restrictions that a superclass has.
+/// A class, mixin, or mixin class declaration prevents inheritance if:
 ///
-/// * A class or mixin prevents inheritance if it's marked interface, or if it
-///   is marked sealed and it extends or mixes in another class which prevents
-///   inheritance.
-/// * We give a warning if a subclass extends or mixes in another class which
-///   prevents inheritance, and the subclass is marked base, or is not marked
-///   `final`, `interface` or `sealed`.
-/// * A class or mixin requires inheritance if it's marked `base`, or if it is
-///   marked `sealed` and it extends or mixes in another class or mixin which
-///   requires inheritance.
-/// * We give a warning if a subclass extends or mixes in another class which
-///   requires inheritance, and the subclass has no modifier or is marked
-///   `interface`.
-/// * A class or mixin prevents subclassing if it's marked `final`, or if it is
-///   marked `sealed` and it extends, mixes in, or implements the interface of
-///   another class or mixin which prevents subclassing.
-/// * We give a warning if a subclass or sub-mixin extends, mixes in, implements
-///   the interface of, or has as an on type a class or mixin which prevents
-///   subclassing, and the subclass or sub-mixin has no modifier or is marked
-///   `interface` or `base`.
+/// * it is marked `interface` or `final`
+/// * it is marked `sealed`, and is implicitly `interface` or `final`
+///   based on the modifiers of its superinterfaces
+/// * it is an anonymous mixin application, and is implicitly `interface` or
+///   `final` based on the modifiers of its superinterfaces
+///
+/// A declaration annotated with `@reopen` will suppress warnings from the
+/// `implicit_reopen` lint. That lint will otherwise warn when a subtype has
+/// restrictions that are not sufficient to enforce the restrictions declared
+/// by class modifiers on one or more superinterfaces.
 ///
 /// In addition, tools, such as the analyzer, can provide feedback if
 ///
-/// * The annotation is applied to anything other than a class or mixin.
+/// * The annotation is applied to anything other than a class, mixin, or mixin
+///   class.
 /// * The annotation is applied to a class or mixin which does not require it.
 ///   (The intent to reopen was not satisfied.)
-@experimental // todo(pq): remove before publishing for 3.0 (https://github.com/dart-lang/sdk/issues/51059)
 const _Reopen reopen = _Reopen();
 
 /// Used to annotate a named parameter `p` in a method or function `f`.
diff --git a/meta/pubspec.yaml b/meta/pubspec.yaml
index 8e42bc2..2696172 100644
--- a/meta/pubspec.yaml
+++ b/meta/pubspec.yaml
@@ -1,13 +1,13 @@
 name: meta
 # Note, because version `2.0.0` was mistakenly released, the next major version must be `3.x.y`.
-version: 1.9.0
+version: 1.9.1
 description: >-
  Annotations used to express developer intentions that can't otherwise be
  deduced by statically analyzing source code.
 repository: https://github.com/dart-lang/sdk/tree/main/pkg/meta
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: ">=2.12.0 <4.0.0"
 
 # We use 'any' version constraints here as we get our package versions from
 # the dart-lang/sdk repo's DEPS file. Note that this is a special case; the
diff --git a/mockito/BUILD.gn b/mockito/BUILD.gn
index 045efdd..1cd0360 100644
--- a/mockito/BUILD.gn
+++ b/mockito/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for mockito-5.3.2
+# This file is generated by package_importer.py for mockito-5.4.0
 
 import("//build/dart/dart_library.gni")
 
 dart_library("mockito") {
   package_name = "mockito"
 
-  language_version = "2.17"
+  language_version = "2.18"
 
   disable_analysis = true
 
diff --git a/mockito/CHANGELOG.md b/mockito/CHANGELOG.md
index 2fb4151..53456ea 100644
--- a/mockito/CHANGELOG.md
+++ b/mockito/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 5.4.0
+
+* Fix nice mocks generation in mixed mode (generated code is pre null-safety,
+  while mocked class is null-safe).
+* Support typedef-aliased classes in `@GenerateMocks` and `@GenerateNiceMocks`
+* **Breaking** Default `generate_for` to `test/**`. Projects that are generating
+  mocks for libraries outside of the `test/` directory should add an explicit
+  `generate_for` argument to include files with mock generating annotations.
+* Require Dart >= 2.17.0.
+* Require analyzer 5.2.0.
+
 ## 5.3.2
 
 * Support analyzer 5.0.0.
diff --git a/mockito/README.md b/mockito/README.md
index 47d394f..a658dbb 100644
--- a/mockito/README.md
+++ b/mockito/README.md
@@ -1,10 +1,9 @@
-# mockito
+[![Dart CI](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml)
+[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito)
+[![package publisher](https://img.shields.io/pub/publisher/mockito.svg)](https://pub.dev/packages/mockito/publisher)
 
 Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito).
 
-[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito)
-[![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito)
-
 ## Let's create mocks
 
 Mockito 5.0.0 supports Dart's new **null safety** language feature in Dart 2.12,
@@ -330,7 +329,7 @@
 }
 ```
 
-The `Cat.sound` method retuns a non-nullable String, but no stub has been made
+The `Cat.sound` method returns a non-nullable String, but no stub has been made
 with `when(cat.sound())`, so what should the code do? What is the "missing stub"
 behavior?
 
diff --git a/mockito/analysis_options.yaml b/mockito/analysis_options.yaml
index 5a16769..0cfb93b 100644
--- a/mockito/analysis_options.yaml
+++ b/mockito/analysis_options.yaml
@@ -1,19 +1,11 @@
-include: package:pedantic/analysis_options.yaml
+include: package:lints/recommended.yaml
+
 analyzer:
-  strong-mode:
-    implicit-casts: false
+  language:
+    strict-casts: true
 
 linter:
   rules:
-    # Errors
     - comment_references
-    - control_flow_in_finally
-    - empty_statements
-    - hash_and_equals
     - test_types_in_equals
-    - throw_in_finally
-
-    # Style
-    - await_only_futures
-    - camel_case_types
-    - non_constant_identifier_names
+    - throw_in_finally
\ No newline at end of file
diff --git a/mockito/build.yaml b/mockito/build.yaml
index baa5dc2..0b4fb40 100644
--- a/mockito/build.yaml
+++ b/mockito/build.yaml
@@ -13,3 +13,5 @@
     build_extensions: {".dart": [".mocks.dart"]}
     build_to: source
     auto_apply: dependents
+    defaults:
+      generate_for: ['test/**']
diff --git a/mockito/lib/src/builder.dart b/mockito/lib/src/builder.dart
index 2935c43..1ad3ea8 100644
--- a/mockito/lib/src/builder.dart
+++ b/mockito/lib/src/builder.dart
@@ -143,7 +143,10 @@
       List<_MockTarget> mockTargets,
       String entryAssetPath,
       LibraryElement entryLib) async {
-    final typeVisitor = _TypeVisitor();
+    // We pass in the `Future<dynamic>` type so that an asset URI is known for
+    // `Future`, which is needed when overriding some methods which return
+    // `FutureOr`.
+    final typeVisitor = _TypeVisitor(entryLib.typeProvider.futureDynamicType);
     final seenTypes = <analyzer.InterfaceType>{};
     final librariesWithTypes = <LibraryElement>{};
 
@@ -153,8 +156,9 @@
         return;
       }
       seenTypes.add(type);
-      librariesWithTypes.add(type.element2.library);
-      type.element2.accept(typeVisitor);
+      librariesWithTypes.add(type.element.library);
+      type.element.accept(typeVisitor);
+      if (type.alias != null) type.alias!.element.accept(typeVisitor);
       // For a type like `Foo<Bar>`, add the `Bar`.
       type.typeArguments
           .whereType<analyzer.InterfaceType>()
@@ -244,6 +248,10 @@
 class _TypeVisitor extends RecursiveElementVisitor<void> {
   final _elements = <Element>{};
 
+  final analyzer.DartType _futureType;
+
+  _TypeVisitor(this._futureType);
+
   @override
   void visitClassElement(ClassElement element) {
     _elements.add(element);
@@ -289,19 +297,25 @@
     super.visitTypeParameterElement(element);
   }
 
+  @override
+  void visitTypeAliasElement(TypeAliasElement element) {
+    _elements.add(element);
+    super.visitTypeAliasElement(element);
+  }
+
   /// Adds [type] to the collected [_elements].
   void _addType(analyzer.DartType? type) {
     if (type == null) return;
 
     if (type is analyzer.InterfaceType) {
-      final alreadyVisitedElement = _elements.contains(type.element2);
-      _elements.add(type.element2);
+      final alreadyVisitedElement = _elements.contains(type.element);
+      _elements.add(type.element);
       type.typeArguments.forEach(_addType);
       if (!alreadyVisitedElement) {
-        type.element2.typeParameters.forEach(visitTypeParameterElement);
+        type.element.typeParameters.forEach(visitTypeParameterElement);
 
         final toStringMethod =
-            type.element2.lookUpMethod('toString', type.element2.library);
+            type.element.lookUpMethod('toString', type.element.library);
         if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) {
           // In a Fake class which implements a class which overrides `toString`
           // with additional (optional) parameters, we must also override
@@ -310,11 +324,16 @@
           for (final parameter in toStringMethod.parameters) {
             final parameterType = parameter.type;
             if (parameterType is analyzer.InterfaceType) {
-              parameterType.element2.accept(this);
+              parameterType.element.accept(this);
             }
           }
         }
       }
+      if (type.isDartAsyncFutureOr) {
+        // For some methods which return `FutureOr`, we need a dummy `Future`
+        // subclass and value.
+        _addType(_futureType);
+      }
     } else if (type is analyzer.FunctionType) {
       _addType(type.returnType);
 
@@ -394,16 +413,17 @@
   final bool hasExplicitTypeArguments;
 
   _MockTarget(
-    this.classType,
-    this.mockName, {
+    this.classType, {
     required this.mixins,
     required this.onMissingStub,
     required this.unsupportedMembers,
     required this.fallbackGenerators,
     this.hasExplicitTypeArguments = false,
-  });
+    String? mockName,
+  }) : mockName = mockName ??
+            'Mock${classType.alias?.element.name ?? classType.element.name}';
 
-  InterfaceElement get interfaceElement => classType.element2;
+  InterfaceElement get interfaceElement => classType.element;
 }
 
 /// This class gathers and verifies mock targets referenced in `GenerateMocks`
@@ -440,7 +460,7 @@
       // annotations, on one element or even on different elements in a library.
       for (final annotation in element.metadata) {
         if (annotation.element is! ConstructorElement) continue;
-        final annotationClass = annotation.element!.enclosingElement3!.name;
+        final annotationClass = annotation.element!.enclosingElement!.name;
         switch (annotationClass) {
           case 'GenerateMocks':
             mockTargets
@@ -497,17 +517,17 @@
         throw InvalidMockitoAnnotationException(
             'Mockito cannot mock `dynamic`');
       }
-      final type = _determineDartType(typeToMock, entryLib.typeProvider);
-      // For a generic class like `Foo<T>` or `Foo<T extends num>`, a type
-      // literal (`Foo`) cannot express type arguments. The type argument(s) on
-      // `type` have been instantiated to bounds here. Switch to the
-      // declaration, which will be an uninstantiated type.
-      final declarationType =
-          (type.element2.declaration as InterfaceElement).thisType;
-      final mockName = 'Mock${declarationType.element2.name}';
+      var type = _determineDartType(typeToMock, entryLib.typeProvider);
+      if (type.alias == null) {
+        // For a generic class without an alias like `Foo<T>` or
+        // `Foo<T extends num>`, a type literal (`Foo`) cannot express type
+        // arguments. The type argument(s) on `type` have been instantiated to
+        // bounds here. Switch to the declaration, which will be an
+        // uninstantiated type.
+        type = (type.element.declaration as InterfaceElement).thisType;
+      }
       mockTargets.add(_MockTarget(
-        declarationType,
-        mockName,
+        type,
         mixins: [],
         onMissingStub: OnMissingStub.throwException,
         unsupportedMembers: {},
@@ -550,35 +570,41 @@
       }
     }
     var type = _determineDartType(typeToMock, entryLib.typeProvider);
-
     final mockTypeArguments = mockType?.typeArguments;
-    if (mockTypeArguments == null) {
-      // The type was given without explicit type arguments. In
-      // this case the type argument(s) on `type` have been instantiated to
-      // bounds. Switch to the declaration, which will be an uninstantiated
-      // type.
-      type = (type.element2.declaration as InterfaceElement).thisType;
-    } else {
+    if (mockTypeArguments != null) {
+      final typeName =
+          type.alias?.element.getDisplayString(withNullability: false) ??
+              'type $type';
+      final typeArguments = type.alias?.typeArguments ?? type.typeArguments;
       // Check explicit type arguments for unknown types that were
       // turned into `dynamic` by the analyzer.
-      type.typeArguments.forEachIndexed((typeArgIdx, typeArgument) {
+      typeArguments.forEachIndexed((typeArgIdx, typeArgument) {
         if (!typeArgument.isDynamic) return;
         if (typeArgIdx >= mockTypeArguments.arguments.length) return;
         final typeArgAst = mockTypeArguments.arguments[typeArgIdx];
         if (typeArgAst is! ast.NamedType) {
           // Is this even possible?
           throw InvalidMockitoAnnotationException(
-              'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type');
+              'Undefined type $typeArgAst passed as the '
+              '${(typeArgIdx + 1).ordinal} type argument for mocked '
+              '$typeName.');
         }
         if (typeArgAst.name.name == 'dynamic') return;
         throw InvalidMockitoAnnotationException(
-          'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type. '
-          'Are you trying to pass to-be-generated mock class as a type argument? Mockito does not support that (yet).',
+          'Undefined type $typeArgAst passed as the '
+          '${(typeArgIdx + 1).ordinal} type argument for mocked $typeName. Are '
+          'you trying to pass to-be-generated mock class as a type argument? '
+          'Mockito does not support that (yet).',
         );
       });
+    } else if (type.alias == null) {
+      // The mock type was given without explicit type arguments. In this case
+      // the type argument(s) on `type` have been instantiated to bounds if this
+      // isn't a type alias. Switch to the declaration, which will be an
+      // uninstantiated type.
+      type = (type.element.declaration as InterfaceElement).thisType;
     }
-    final mockName = mockSpec.getField('mockName')!.toSymbolValue() ??
-        'Mock${type.element2.name}';
+    final mockName = mockSpec.getField('mockName')!.toSymbolValue();
     final mixins = <analyzer.InterfaceType>[];
     for (final m in mockSpec.getField('mixins')!.toListValue()!) {
       final typeToMixin = m.toTypeValue();
@@ -592,11 +618,6 @@
       }
       final mixinInterfaceType =
           _determineDartType(typeToMixin, entryLib.typeProvider);
-      if (!mixinInterfaceType.interfaces.contains(type)) {
-        throw InvalidMockitoAnnotationException('The "mixingIn" type, '
-            '${typeToMixin.getDisplayString(withNullability: false)}, must '
-            'implement the class to mock, ${typeToMock.getDisplayString(withNullability: false)}');
-      }
       mixins.add(mixinInterfaceType);
     }
 
@@ -650,7 +671,7 @@
         mockSpec.getField('fallbackGenerators')!.toMapValue()!;
     return _MockTarget(
       type,
-      mockName,
+      mockName: mockName,
       mixins: mixins,
       onMissingStub: onMissingStub,
       unsupportedMembers: unsupportedMembers,
@@ -702,7 +723,7 @@
   static analyzer.InterfaceType _determineDartType(
       analyzer.DartType typeToMock, TypeProvider typeProvider) {
     if (typeToMock is analyzer.InterfaceType) {
-      final elementToMock = typeToMock.element2;
+      final elementToMock = typeToMock.element;
       if (elementToMock is EnumElement) {
         throw InvalidMockitoAnnotationException(
             'Mockito cannot mock an enum: ${elementToMock.displayName}');
@@ -728,17 +749,17 @@
             "'${elementToMock.displayName}' for the following reasons:\n"
             '$joinedMessages');
       }
+      if (typeToMock.alias != null &&
+          typeToMock.nullabilitySuffix == NullabilitySuffix.question) {
+        throw InvalidMockitoAnnotationException(
+            'Mockito cannot mock a type-aliased nullable type: '
+            '${typeToMock.alias!.element.name}');
+      }
       return typeToMock;
     }
 
-    var aliasElement = typeToMock.alias?.element;
-    if (aliasElement != null) {
-      throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: '
-          '${aliasElement.displayName}');
-    } else {
-      throw InvalidMockitoAnnotationException(
-          'Mockito cannot mock a non-class: $typeToMock');
-    }
+    throw InvalidMockitoAnnotationException('Mockito cannot mock a non-class: '
+        '${typeToMock.alias?.element.name ?? typeToMock.toString()}');
   }
 
   void _checkClassesToMockAreValid() {
@@ -775,7 +796,7 @@
 
       var preexistingMock = classesInEntryLib.firstWhereOrNull((c) =>
           c.interfaces
-              .map((type) => type.element2)
+              .map((type) => type.element)
               .contains(mockTarget.interfaceElement) &&
           _isMockClass(c.supertype!));
       if (preexistingMock != null) {
@@ -899,7 +920,7 @@
     for (var parameter in function.parameters) {
       var parameterType = parameter.type;
       if (parameterType is analyzer.InterfaceType) {
-        var parameterTypeElement = parameterType.element2;
+        var parameterTypeElement = parameterType.element;
         if (parameterTypeElement.isPrivate) {
           // Technically, we can expand the type in the mock to something like
           // `Object?`. However, until there is a decent use case, we will not
@@ -946,7 +967,7 @@
       if (typeParameter is analyzer.InterfaceType) {
         // TODO(srawlins): Check for private names in bound; could be
         // `List<_Bar>`.
-        if (typeParameter.element2.isPrivate) {
+        if (typeParameter.element.isPrivate) {
           errorMessages.add(
               '${enclosingElement.fullName} features a private type parameter '
               'bound, and cannot be stubbed.');
@@ -970,7 +991,7 @@
     var errorMessages = <String>[];
     for (var typeArgument in typeArguments) {
       if (typeArgument is analyzer.InterfaceType) {
-        if (typeArgument.element2.isPrivate && !allowUnsupportedMember) {
+        if (typeArgument.element.isPrivate && !allowUnsupportedMember) {
           errorMessages.add(
               '${enclosingElement.fullName} features a private type argument, '
               'and cannot be stubbed. $_tryUnsupportedMembersMessage');
@@ -989,8 +1010,8 @@
 
   /// Return whether [type] is the Mock class declared by mockito.
   bool _isMockClass(analyzer.InterfaceType type) =>
-      type.element2.name == 'Mock' &&
-      type.element2.source.fullName.endsWith('lib/src/mock.dart');
+      type.element.name == 'Mock' &&
+      type.element.source.fullName.endsWith('lib/src/mock.dart');
 }
 
 class _MockLibraryInfo {
@@ -1085,10 +1106,14 @@
   });
 
   Class _buildMockClass() {
-    final typeToMock = mockTarget.classType;
+    final typeAlias = mockTarget.classType.alias;
+    final aliasedElement = typeAlias?.element;
+    final aliasedType =
+        typeAlias?.element.aliasedType as analyzer.InterfaceType?;
+    final typeToMock = aliasedType ?? mockTarget.classType;
     final classToMock = mockTarget.interfaceElement;
     final classIsImmutable = classToMock.metadata.any((it) => it.isImmutable);
-    final className = classToMock.name;
+    final className = aliasedElement?.name ?? classToMock.name;
 
     return Class((cBuilder) {
       cBuilder
@@ -1106,50 +1131,70 @@
       // For each type parameter on [classToMock], the Mock class needs a type
       // parameter with same type variables, and a mirrored type argument for
       // the "implements" clause.
-      var typeArguments = <Reference>[];
+      final typeReferences = <Reference>[];
+
+      final typeParameters =
+          aliasedElement?.typeParameters ?? classToMock.typeParameters;
+      final typeArguments =
+          typeAlias?.typeArguments ?? typeToMock.typeArguments;
+
       if (mockTarget.hasExplicitTypeArguments) {
         // [typeToMock] is a reference to a type with type arguments (for
         // example: `Foo<int>`). Generate a non-generic mock class which
         // implements the mock target with said type arguments. For example:
         // `class MockFoo extends Mock implements Foo<int> {}`
-        for (var typeArgument in typeToMock.typeArguments) {
-          typeArguments.add(_typeReference(typeArgument));
+        for (var typeArgument in typeArguments) {
+          typeReferences.add(_typeReference(typeArgument));
         }
       } else {
         // [typeToMock] is a simple reference to a generic type (for example:
         // `Foo`, a reference to `class Foo<T> {}`). Generate a generic mock
         // class which perfectly mirrors the type parameters on [typeToMock],
         // forwarding them to the "implements" clause.
-        for (var typeParameter in classToMock.typeParameters) {
+        for (var typeParameter in typeParameters) {
           cBuilder.types.add(_typeParameterReference(typeParameter));
-          typeArguments.add(refer(typeParameter.name));
+          typeReferences.add(refer(typeParameter.name));
         }
       }
+
       for (final mixin in mockTarget.mixins) {
         cBuilder.mixins.add(TypeReference((b) {
           b
-            ..symbol = mixin.element2.name
-            ..url = _typeImport(mixin.element2)
+            ..symbol = mixin.element.name
+            ..url = _typeImport(mixin.element)
             ..types.addAll(mixin.typeArguments.map(_typeReference));
         }));
       }
       cBuilder.implements.add(TypeReference((b) {
         b
-          ..symbol = classToMock.name
-          ..url = _typeImport(mockTarget.interfaceElement)
-          ..types.addAll(typeArguments);
+          ..symbol = className
+          ..url = _typeImport(aliasedElement ?? classToMock)
+          ..types.addAll(typeReferences);
       }));
       if (mockTarget.onMissingStub == OnMissingStub.throwException) {
         cBuilder.constructors.add(_constructorWithThrowOnMissingStub);
       }
 
-      final substitution = Substitution.fromInterfaceType(typeToMock);
+      final substitution = Substitution.fromPairs([
+        ...classToMock.typeParameters,
+        ...?aliasedElement?.typeParameters,
+      ], [
+        ...typeToMock.typeArguments,
+        ...?typeAlias?.typeArguments,
+      ]);
       final members =
           inheritanceManager.getInterface(classToMock).map.values.map((member) {
         return ExecutableMember.from2(member, substitution);
       });
 
-      if (sourceLibIsNonNullable) {
+      // The test can be pre-null-safety but if the class
+      // we want to mock is defined in a null safe library,
+      // we still need to override methods to get nice mocks.
+      final isNiceMockOfNullSafeClass = mockTarget.onMissingStub ==
+              OnMissingStub.returnDefault &&
+          typeToMock.element.enclosingElement.library.isNonNullableByDefault;
+
+      if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) {
         cBuilder.methods.addAll(
             fieldOverrides(members.whereType<PropertyAccessorElement>()));
         cBuilder.methods
@@ -1193,12 +1238,23 @@
       if (accessor.isGetter && typeSystem._returnTypeIsNonNullable(accessor)) {
         yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor));
       }
-      if (accessor.isSetter) {
+      if (accessor.isSetter && sourceLibIsNonNullable) {
         yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor));
       }
     }
   }
 
+  bool _methodNeedsOverride(MethodElement method) {
+    if (!sourceLibIsNonNullable) {
+      // If we get here, we are adding overrides only to make
+      // nice mocks work. We only care about return types then.
+      return typeSystem._returnTypeIsNonNullable(method);
+    }
+    return typeSystem._returnTypeIsNonNullable(method) ||
+        typeSystem._hasNonNullableParameter(method) ||
+        _needsOverrideForVoidStub(method);
+  }
+
   /// Yields all of the method overrides required for [methods].
   ///
   /// This includes methods of supertypes and mixed in types.
@@ -1225,9 +1281,7 @@
         // narrow the return type.
         continue;
       }
-      if (typeSystem._returnTypeIsNonNullable(method) ||
-          typeSystem._hasNonNullableParameter(method) ||
-          _needsOverrideForVoidStub(method)) {
+      if (_methodNeedsOverride(method)) {
         _checkForConflictWithCore(method.name);
         yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method));
       }
@@ -1328,8 +1382,10 @@
       }
       builder.body = refer('UnsupportedError')
           .call([
+            // Generate a raw string since name might contain a $.
             literalString(
-                "'$name' cannot be used without a mockito fallback generator.")
+                '"$name" cannot be used without a mockito fallback generator.',
+                raw: true)
           ])
           .thrown
           .code;
@@ -1419,13 +1475,24 @@
       return refer('() {}');
     } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) {
       final typeArgument = typeArguments.first;
-      final futureValueArguments =
-          typeSystem.isPotentiallyNonNullable(typeArgument)
-              ? [_dummyValue(typeArgument, invocation)]
-              : <Expression>[];
-      return _futureReference(_typeReference(typeArgument))
-          .property('value')
-          .call(futureValueArguments);
+      final typeArgumentIsPotentiallyNonNullable =
+          typeSystem.isPotentiallyNonNullable(typeArgument);
+      if (typeArgument is analyzer.TypeParameterType &&
+          typeArgumentIsPotentiallyNonNullable) {
+        // We cannot create a valid Future for this unknown, potentially
+        // non-nullable type, so we'll use a `_FakeFuture`, which will throw
+        // if awaited.
+        var futureType = typeProvider.futureType(typeArguments.first);
+        return _dummyValueImplementing(futureType, invocation);
+      } else {
+        // Create a real Future with a legal value, via [Future.value].
+        final futureValueArguments = typeArgumentIsPotentiallyNonNullable
+            ? [_dummyValue(typeArgument, invocation)]
+            : <Expression>[];
+        return _futureReference(_typeReference(typeArgument))
+            .property('value')
+            .call(futureValueArguments);
+      }
     } else if (type.isDartCoreInt) {
       return literalNum(0);
     } else if (type.isDartCoreIterable || type.isDartCoreList) {
@@ -1443,7 +1510,7 @@
       assert(typeArguments.length == 1);
       final elementType = _typeReference(typeArguments[0]);
       return literalSet({}, elementType);
-    } else if (type.element2.declaration == typeProvider.streamElement) {
+    } else if (type.element.declaration == typeProvider.streamElement) {
       assert(typeArguments.length == 1);
       final elementType = _typeReference(typeArguments[0]);
       return TypeReference((b) {
@@ -1454,11 +1521,12 @@
       }).property('empty').call([]);
     } else if (type.isDartCoreString) {
       return literalString('');
-    } else if (type.isDartTypedDataList) {
-      // These "List" types from dart:typed_data are "non-subtypeable", but they
+    } else if (type.isDartTypedDataSealed) {
+      // These types (XXXList + ByteData) from dart:typed_data are
+      // sealed, e.g. "non-subtypeable", but they
       // have predicatble constructors; each has an unnamed constructor which
       // takes a single int argument.
-      return referImported(type.element2.name, 'dart:typed_data')
+      return referImported(type.element.name, 'dart:typed_data')
           .call([literalNum(0)]);
       // TODO(srawlins): Do other types from typed_data have a "non-subtypeable"
       // restriction as well?
@@ -1518,7 +1586,7 @@
 
   Expression _dummyValueImplementing(
       analyzer.InterfaceType dartType, Expression invocation) {
-    final elementToFake = dartType.element2;
+    final elementToFake = dartType.element;
     if (elementToFake is EnumElement) {
       return _typeReference(dartType).property(
           elementToFake.fields.firstWhere((f) => f.isEnumConstant).name);
@@ -1601,18 +1669,20 @@
     return Parameter((pBuilder) {
       pBuilder.name = name;
       if (!superParameterType.containsPrivateName) {
-        pBuilder.type =
-            _typeReference(superParameterType, forceNullable: forceNullable);
+        pBuilder.type = _typeReference(superParameterType,
+            forceNullable: forceNullable, overrideVoid: true);
       }
       if (parameter.isNamed) pBuilder.named = true;
-      if (parameter.isRequiredNamed) pBuilder.required = true;
+      if (parameter.isRequiredNamed && sourceLibIsNonNullable) {
+        pBuilder.required = true;
+      }
       if (parameter.defaultValueCode != null) {
         try {
           pBuilder.defaultTo = _expressionFromDartObject(
                   parameter.computeConstantValue()!, parameter)
               .code;
         } on _ReviveException catch (e) {
-          final method = parameter.enclosingElement3!;
+          final method = parameter.enclosingElement!;
           throw InvalidMockitoAnnotationException(
               'Mockito cannot generate a valid override for method '
               "'${mockTarget.interfaceElement.displayName}.${method.displayName}'; "
@@ -1643,8 +1713,8 @@
     if (!parameter.isCovariant) {
       return type;
     }
-    final method = parameter.enclosingElement3 as MethodElement;
-    final class_ = method.enclosingElement3 as InterfaceElement;
+    final method = parameter.enclosingElement as MethodElement;
+    final class_ = method.enclosingElement as InterfaceElement;
     final name = Name(method.librarySource.uri, method.name);
     final overriddenMethods = inheritanceManager.getOverridden2(class_, name);
     if (overriddenMethods == null) {
@@ -1654,7 +1724,7 @@
     while (allOverriddenMethods.isNotEmpty) {
       final overriddenMethod = allOverriddenMethods.removeFirst();
       final secondaryOverrides = inheritanceManager.getOverridden2(
-          overriddenMethod.enclosingElement3 as InterfaceElement, name);
+          overriddenMethod.enclosingElement as InterfaceElement, name);
       if (secondaryOverrides != null) {
         allOverriddenMethods.addAll(secondaryOverrides);
       }
@@ -1745,7 +1815,7 @@
         // We can create this invocation by referring to a const field or
         // top-level variable.
         return referImported(
-            revivable.accessor, _typeImport(object.type!.element2));
+            revivable.accessor, _typeImport(object.type!.element));
       }
 
       final name = revivable.source.fragment;
@@ -1757,9 +1827,9 @@
         for (var pair in revivable.namedArguments.entries)
           pair.key: _expressionFromDartObject(pair.value)
       };
-      final element = parameter != null && name != object.type!.element2!.name
-          ? parameter.type.element2
-          : object.type!.element2;
+      final element = parameter != null && name != object.type!.element!.name
+          ? parameter.type.element
+          : object.type!.element;
       final type = referImported(name, _typeImport(element));
       if (revivable.accessor.isNotEmpty) {
         return type.constInstanceNamed(
@@ -1806,9 +1876,11 @@
       }
       builder.body = refer('UnsupportedError')
           .call([
+            // Generate a raw string since getter.name might contain a $.
             literalString(
-                "'${getter.name}' cannot be used without a mockito fallback "
-                'generator.')
+                '"${getter.name}" cannot be used without a mockito fallback '
+                'generator.',
+                raw: true)
           ])
           .thrown
           .code;
@@ -1856,7 +1928,8 @@
     builder.requiredParameters.add(Parameter((pBuilder) {
       pBuilder.name = parameter.displayName;
       if (!parameter.type.containsPrivateName) {
-        pBuilder.type = _typeReference(parameter.type, forceNullable: true);
+        pBuilder.type = _typeReference(parameter.type,
+            forceNullable: true, overrideVoid: true);
       }
     }));
 
@@ -1870,9 +1943,11 @@
       }
       builder.body = refer('UnsupportedError')
           .call([
+            // Generate a raw string since nameWithEquals might contain a $.
             literalString(
-                "'$nameWithEquals' cannot be used without a mockito fallback "
-                'generator.')
+                '"$nameWithEquals" cannot be used without a mockito fallback '
+                'generator.',
+                raw: true)
           ])
           .thrown
           .code;
@@ -1916,14 +1991,18 @@
   // TODO(srawlins): Contribute this back to a common location, like
   // package:source_gen?
   Reference _typeReference(analyzer.DartType type,
-      {bool forceNullable = false}) {
+      {bool forceNullable = false, bool overrideVoid = false}) {
+    if (overrideVoid && type.isVoid) {
+      return TypeReference((b) => b..symbol = 'dynamic');
+    }
     if (type is analyzer.InterfaceType) {
       return TypeReference((b) {
         b
-          ..symbol = type.element2.name
-          ..isNullable = forceNullable ||
-              type.nullabilitySuffix == NullabilitySuffix.question
-          ..url = _typeImport(type.element2)
+          ..symbol = type.element.name
+          ..isNullable = !type.isDartCoreNull &&
+              (forceNullable ||
+                  type.nullabilitySuffix == NullabilitySuffix.question)
+          ..url = _typeImport(type.element)
           ..types.addAll(type.typeArguments.map(_typeReference));
       });
     } else if (type is analyzer.FunctionType) {
@@ -1964,13 +2043,13 @@
     } else if (type is analyzer.TypeParameterType) {
       return TypeReference((b) {
         b
-          ..symbol = type.element2.name
+          ..symbol = type.element.name
           ..isNullable = forceNullable || typeSystem.isNullable(type);
       });
     } else {
       return referImported(
         type.getDisplayString(withNullability: false),
-        _typeImport(type.element2),
+        _typeImport(type.element),
       );
     }
   }
@@ -2086,12 +2165,12 @@
     } else if (this is EnumElement) {
       return "The enum '$name'";
     } else if (this is MethodElement) {
-      var className = enclosingElement3!.name;
+      var className = enclosingElement!.name;
       return "The method '$className.$name'";
     } else if (this is MixinElement) {
       return "The mixin '$name'";
     } else if (this is PropertyAccessorElement) {
-      var className = enclosingElement3!.name;
+      var className = enclosingElement!.name;
       return "The property accessor '$className.$name'";
     } else {
       return 'unknown element';
@@ -2107,7 +2186,7 @@
     if (self is analyzer.DynamicType) {
       return false;
     } else if (self is analyzer.InterfaceType) {
-      return self.element2.isPrivate ||
+      return self.element.isPrivate ||
           self.typeArguments.any((t) => t.containsPrivateName);
     } else if (self is analyzer.FunctionType) {
       return self.returnType.containsPrivateName ||
@@ -2129,13 +2208,13 @@
       isDartAsyncFuture &&
       (this as analyzer.InterfaceType).typeArguments.first.isVoid;
 
-  /// Returns whether this type is a "List" type from the dart:typed_data
+  /// Returns whether this type is a sealed type from the dart:typed_data
   /// library.
-  bool get isDartTypedDataList {
-    if (element2!.library!.name != 'dart.typed_data') {
+  bool get isDartTypedDataSealed {
+    if (element!.library!.name != 'dart.typed_data') {
       return false;
     }
-    final name = element2!.name;
+    final name = element!.name;
     return name == 'Float32List' ||
         name == 'Float64List' ||
         name == 'Int8List' ||
@@ -2145,7 +2224,8 @@
         name == 'Uint8List' ||
         name == 'Uint16List' ||
         name == 'Uint32List' ||
-        name == 'Uint64List';
+        name == 'Uint64List' ||
+        name == 'ByteData';
   }
 }
 
@@ -2172,11 +2252,11 @@
   String get ordinal {
     final remainder = this % 10;
     switch (remainder) {
-      case (1):
+      case 1:
         return '${this}st';
-      case (2):
+      case 2:
         return '${this}nd';
-      case (3):
+      case 3:
         return '${this}rd';
       default:
         return '${this}th';
diff --git a/mockito/lib/src/mock.dart b/mockito/lib/src/mock.dart
index 3201709..21c2d5d 100644
--- a/mockito/lib/src/mock.dart
+++ b/mockito/lib/src/mock.dart
@@ -228,11 +228,40 @@
   final Object _parent;
   final Invocation _parentInvocation;
   final StackTrace _createdStackTrace;
+  late final StackTrace _toStringFirstCalled = StackTrace.current;
+
+  // Override [Object.operator==] to accept `Object?`. This is needed to
+  // make Analyzer happy, if we fake classes that override `==` to
+  // accept `Object?` or `dynamic` (most notably [Interceptor]).
+  @override
+  // ignore: non_nullable_equals_parameter
+  bool operator ==(Object? other) => identical(this, other);
+
   @override
   dynamic noSuchMethod(Invocation invocation) => throw FakeUsedError(
       _parentInvocation, invocation, _parent, _createdStackTrace);
   SmartFake(this._parent, this._parentInvocation)
       : _createdStackTrace = StackTrace.current;
+
+  @override
+  String toString() {
+    final memberName = _symbolToString(_parentInvocation.memberName);
+    return '''Fake object created as a result of calling unstubbed member
+$memberName of a mock.
+
+Here is the stack trace where $memberName was called:
+
+$_createdStackTrace
+
+Normally you would never see this message, if it breaks your test,
+consider adding a stub for ${_parent.runtimeType}.$memberName using Mockito's
+'when' API.
+
+Here is the stack trace where toString() was first called:
+
+$_toStringFirstCalled
+''';
+  }
 }
 
 class FakeUsedError extends Error {
@@ -309,7 +338,8 @@
   factory _InvocationForMatchedArguments(Invocation invocation) {
     if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) {
       throw StateError(
-          '_InvocationForMatchedArguments called when no ArgMatchers have been saved.');
+          '_InvocationForMatchedArguments called when no ArgMatchers have been '
+          'saved.');
     }
 
     // Handle named arguments first, so that we can provide useful errors for
@@ -825,9 +855,9 @@
     _storedNamedArgs.clear();
     throw ArgumentError(
         'The "$argumentMatcher" argument matcher is used outside of method '
-        'stubbing (via `when`) or verification (via `verify` or `untilCalled`). '
-        'This is invalid, and results in bad behavior during the next stubbing '
-        'or verification.');
+        'stubbing (via `when`) or verification (via `verify` or '
+        '`untilCalled`). This is invalid, and results in bad behavior during '
+        'the next stubbing or verification.');
   }
   var argMatcher = ArgMatcher(matcher, capture);
   if (named == null) {
@@ -918,9 +948,6 @@
 
 typedef Verification = VerificationResult Function<T>(T matchingInvocations);
 
-typedef _InOrderVerification = List<VerificationResult> Function<T>(
-    List<T> recordedInvocations);
-
 /// Verify that a method on a mock object was never called with the given
 /// arguments.
 ///
@@ -1029,7 +1056,8 @@
 /// given, but not that those were the only calls. In the example above, if
 /// other calls were made to `eatFood` or `sound` between the three given
 /// calls, or before or after them, the verification will still succeed.
-_InOrderVerification get verifyInOrder {
+List<VerificationResult> Function<T>(List<T> recordedInvocations)
+    get verifyInOrder {
   if (_verifyCalls.isNotEmpty) {
     throw StateError(_verifyCalls.join());
   }
@@ -1084,7 +1112,7 @@
   if (mock is Mock) {
     var unverified = mock._realCalls.where((inv) => !inv.verified).toList();
     if (unverified.isNotEmpty) {
-      fail('No more calls expected, but following found: ' + unverified.join());
+      fail('No more calls expected, but following found: ${unverified.join()}');
     }
   } else {
     _throwMockArgumentError('verifyNoMoreInteractions', mock);
@@ -1094,8 +1122,8 @@
 void verifyZeroInteractions(var mock) {
   if (mock is Mock) {
     if (mock._realCalls.isNotEmpty) {
-      fail('No interaction expected, but following found: ' +
-          mock._realCalls.join());
+      fail('No interaction expected, but following found: '
+          '${mock._realCalls.join()}');
     }
   } else {
     _throwMockArgumentError('verifyZeroInteractions', mock);
@@ -1160,9 +1188,9 @@
   var allInvocations =
       mocks.expand((m) => m._realCalls).toList(growable: false);
   allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp));
-  allInvocations.forEach((inv) {
+  for (var inv in allInvocations) {
     print(inv.toString());
-  });
+  }
 }
 
 /// Reset the state of Mockito, typically for use between tests.
@@ -1198,10 +1226,9 @@
     if (args.any((arg) => arg.contains('\n'))) {
       // As one or more arg contains newlines, put each on its own line, and
       // indent each, for better readability.
-      argString = '\n' +
-          args
-              .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => '    $m'))
-              .join(',\n');
+      var indentedArgs = args
+          .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => '    $m'));
+      argString = '\n${indentedArgs.join(',\n')}';
     } else {
       // A compact String should be perfect.
       argString = args.join(', ');
@@ -1226,7 +1253,7 @@
     if (isMethod) {
       method = '$method($argString)';
     } else if (isGetter) {
-      method = '$method';
+      method = method;
     } else if (isSetter) {
       method = '$method=$argString';
     } else {
diff --git a/mockito/lib/src/version.dart b/mockito/lib/src/version.dart
index cc93276..3dfd2c8 100644
--- a/mockito/lib/src/version.dart
+++ b/mockito/lib/src/version.dart
@@ -1 +1 @@
-const packageVersion = '5.3.2';
+const packageVersion = '5.4.0';
diff --git a/mockito/pubspec.yaml b/mockito/pubspec.yaml
index df10468..060205f 100644
--- a/mockito/pubspec.yaml
+++ b/mockito/pubspec.yaml
@@ -1,30 +1,30 @@
 name: mockito
-version: 5.3.2
+version: 5.4.0
 description: >-
   A mock framework inspired by Mockito with APIs for Fakes, Mocks,
   behavior verification, and stubbing.
 repository: https://github.com/dart-lang/mockito
 
 environment:
-  sdk: '>=2.17.0-0 <3.0.0'
+  sdk: '>=2.18.0 <3.0.0'
 
 dependencies:
-  analyzer: '>=4.7.0 <6.0.0'
+  analyzer: '>=5.2.0 <6.0.0'
   build: '>=1.3.0 <3.0.0'
-  code_builder: ^4.3.0
+  code_builder: ^4.2.0
   collection: ^1.15.0
   dart_style: '>=1.3.6 <3.0.0'
   matcher: ^0.12.10
   meta: ^1.3.0
   path: ^1.8.0
   source_gen: '>=0.9.6 <2.0.0'
-  test_api: '>=0.2.1 <0.5.0'
+  test_api: '>=0.2.1 <0.6.0'
 
 dev_dependencies:
   build_runner: ^2.0.0
   build_test: ^2.0.0
-  build_web_compilers: ^3.0.0
+  build_web_compilers: '>=3.0.0 <5.0.0'
   http: ^0.13.0
+  lints: ^2.0.0
   package_config: '>=1.9.3 <3.0.0'
-  pedantic: ^1.10.0
   test: ^1.16.0
diff --git a/node_preamble/.gitignore b/node_preamble/.gitignore
deleted file mode 100644
index d0a80b8..0000000
--- a/node_preamble/.gitignore
+++ /dev/null
@@ -1,18 +0,0 @@
-# Don’t commit the following directories created by pub.
-.buildlog
-.dart_tool/
-.pub/
-build/
-packages
-.packages
-
-# Or the files created by dart2js.
-*.dart.js
-*.js_
-*.js.deps
-*.js.map
-
-# Include when developing application packages.
-pubspec.lock
-
-node_modules
diff --git a/node_preamble/BUILD.gn b/node_preamble/BUILD.gn
index 63e34c4..1eaa6b7 100644
--- a/node_preamble/BUILD.gn
+++ b/node_preamble/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for node_preamble-2.0.1
+# This file is generated by package_importer.py for node_preamble-2.0.2
 
 import("//build/dart/dart_library.gni")
 
diff --git a/node_preamble/CHANGELOG.md b/node_preamble/CHANGELOG.md
index fcfc302..ffb035d 100644
--- a/node_preamble/CHANGELOG.md
+++ b/node_preamble/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 2.0.2
+
+* Don't crash when running in browser environments.
+
 # 2.0.1
 
 * Define properties for `global.location` and `global.document` rather than
diff --git a/node_preamble/lib/preamble.dart b/node_preamble/lib/preamble.dart
index fdae6bb..f8f4015 100644
--- a/node_preamble/lib/preamble.dart
+++ b/node_preamble/lib/preamble.dart
@@ -1,14 +1,13 @@
 library node_preamble;
 
-final _minified = r"""var dartNodePreambleSelf="undefined"!=typeof global?global:window,self=Object.create(dartNodePreambleSelf);if(self.scheduleImmediate="undefined"!=typeof setImmediate?function(e){setImmediate(e)}:function(e){setTimeout(e,0)},self.require=require,self.exports=exports,"undefined"!=typeof process)self.process=process;if("undefined"!=typeof __dirname)self.__dirname=__dirname;if("undefined"!=typeof __filename)self.__filename=__filename;if("undefined"!=typeof Buffer)self.Buffer=Buffer;var dartNodeIsActuallyNode=!dartNodePreambleSelf.window;try{if("undefined"!=typeof WorkerGlobalScope&&dartNodePreambleSelf instanceof WorkerGlobalScope)dartNodeIsActuallyNode=!1;if("undefined"!=typeof process&&process.versions&&process.versions.hasOwnProperty("electron")&&process.versions.hasOwnProperty("node"))dartNodeIsActuallyNode=!0}catch(e){}if(dartNodeIsActuallyNode){var url=("undefined"!=typeof __webpack_require__?__non_webpack_require__:require)("url");Object.defineProperty(self,"location",{value:{get href(){if(url.pathToFileURL)return url.pathToFileURL(process.cwd()).href+"/";else return"file://"+function(){var e=process.cwd();if("win32"!=process.platform)return e;else return"/"+e.replace(/\\/g,"/")}()+"/"}}}),function(){function e(){try{throw new Error}catch(n){var e=n.stack,r=new RegExp("^ *at [^(]*\\((.*):[0-9]*:[0-9]*\\)$","mg"),o=null;do{var t=r.exec(e);if(null!=t)o=t}while(null!=t);return o[1]}}var r=null;Object.defineProperty(self,"document",{value:{get currentScript(){if(null==r)r={src:e()};return r}}})}(),self.dartDeferredLibraryLoader=function(e,r,o){try{load(e),r()}catch(e){o(e)}}}""";
+final _minified = r"""var dartNodeIsActuallyNode="undefined"!=typeof process&&(process.versions||{}).hasOwnProperty("node"),self=dartNodeIsActuallyNode?Object.create(globalThis):globalThis;if(self.scheduleImmediate="undefined"!=typeof setImmediate?function(e){setImmediate(e)}:function(e){setTimeout(e,0)},"undefined"!=typeof require)self.require=require;if("undefined"!=typeof exports)self.exports=exports;if("undefined"!=typeof process)self.process=process;if("undefined"!=typeof __dirname)self.__dirname=__dirname;if("undefined"!=typeof __filename)self.__filename=__filename;if("undefined"!=typeof Buffer)self.Buffer=Buffer;if(dartNodeIsActuallyNode){var url=("undefined"!=typeof __webpack_require__?__non_webpack_require__:require)("url");Object.defineProperty(self,"location",{value:{get href(){if(url.pathToFileURL)return url.pathToFileURL(process.cwd()).href+"/";else return"file://"+function(){var e=process.cwd();if("win32"!=process.platform)return e;else return"/"+e.replace(/\\/g,"/")}()+"/"}}}),function(){function e(){try{throw new Error}catch(n){var e=n.stack,r=new RegExp("^ *at [^(]*\\((.*):[0-9]*:[0-9]*\\)$","mg"),f=null;do{var t=r.exec(e);if(null!=t)f=t}while(null!=t);return f[1]}}var r=null;Object.defineProperty(self,"document",{value:{get currentScript(){if(null==r)r={src:e()};return r}}})}(),self.dartDeferredLibraryLoader=function(e,r,f){try{load(e),r()}catch(e){f(e)}}}""";
 
 final _normal = r"""
+var dartNodeIsActuallyNode = typeof process !== "undefined" && (process.versions || {}).hasOwnProperty('node');
+
 // make sure to keep this as 'var'
 // we don't want block scoping
-
-var dartNodePreambleSelf = typeof global !== "undefined" ? global : window;
-
-var self = Object.create(dartNodePreambleSelf);
+var self = dartNodeIsActuallyNode ? Object.create(globalThis) : globalThis;
 
 self.scheduleImmediate = typeof setImmediate !== "undefined"
     ? function (cb) {
@@ -19,8 +18,12 @@
       };
 
 // CommonJS globals.
-self.require = require;
-self.exports = exports;
+if (typeof require !== "undefined") {
+  self.require = require;
+}
+if (typeof exports !== "undefined") {
+  self.exports = exports;
+}
 
 // Node.js specific exports, check to see if they exist & or polyfilled
 
@@ -43,20 +46,6 @@
 // if we're running in a browser, Dart supports most of this out of box
 // make sure we only run these in Node.js environment
 
-var dartNodeIsActuallyNode = !dartNodePreambleSelf.window
-
-try {
-  // Check if we're in a Web Worker instead.
-  if ("undefined" !== typeof WorkerGlobalScope && dartNodePreambleSelf instanceof WorkerGlobalScope) {
-    dartNodeIsActuallyNode = false;
-  }
-
-  // Check if we're in Electron, with Node.js integration, and override if true.
-  if ("undefined" !== typeof process && process.versions && process.versions.hasOwnProperty('electron') && process.versions.hasOwnProperty('node')) {
-    dartNodeIsActuallyNode = true;
-  }
-} catch(e) {}
-
 if (dartNodeIsActuallyNode) {
   // This line is to:
   // 1) Prevent Webpack from bundling.
diff --git a/node_preamble/package.json b/node_preamble/package.json
index 2807441..ce5c0ea 100644
--- a/node_preamble/package.json
+++ b/node_preamble/package.json
@@ -1,6 +1,6 @@
 {
   "dependencies": {
-    "terser": "^4.2.1"
+    "terser": "^4.8.1"
   },
   "scripts": {
     "minify": "node tool/minify.js"
diff --git a/node_preamble/pubspec.yaml b/node_preamble/pubspec.yaml
index 7e0945f..935314a 100644
--- a/node_preamble/pubspec.yaml
+++ b/node_preamble/pubspec.yaml
@@ -1,7 +1,7 @@
 name: node_preamble
 author: Michael Bullington <mikebullingtn@gmail.com>
 homepage: https://github.com/mbullington/node_preamble.dart
-version: 2.0.1
+version: 2.0.2
 description: Better node.js preamble for dart2js, use it in your build system.
 
 environment:
diff --git a/node_preamble/yarn.lock b/node_preamble/yarn.lock
index fdca61a..66d3edf 100644
--- a/node_preamble/yarn.lock
+++ b/node_preamble/yarn.lock
@@ -25,10 +25,10 @@
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
-terser@^4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-4.2.1.tgz#1052cfe17576c66e7bc70fcc7119f22b155bdac1"
-  integrity sha512-cGbc5utAcX4a9+2GGVX4DsenG6v0x3glnDi5hx8816X1McEAwPlPgRtXPJzSBsbpILxZ8MQMT0KvArLuE0HP5A==
+terser@^4.8.1:
+  version "4.8.1"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f"
+  integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==
   dependencies:
     commander "^2.20.0"
     source-map "~0.6.1"
diff --git a/package_config.json b/package_config.json
index 9d4b24b..33e32ed 100644
--- a/package_config.json
+++ b/package_config.json
@@ -3,7 +3,7 @@
   "generator": "package_importer.py",
   "packages": [
     {
-      "languageVersion": "2.17",
+      "languageVersion": "2.19",
       "name": "_discoveryapis_commons",
       "packageUri": "lib/",
       "rootUri": "./_discoveryapis_commons/"
@@ -15,7 +15,7 @@
       "rootUri": "./_fe_analyzer_shared/"
     },
     {
-      "languageVersion": "2.17",
+      "languageVersion": "2.19",
       "name": "analyzer",
       "packageUri": "lib/",
       "rootUri": "./analyzer/"
@@ -51,7 +51,7 @@
       "rootUri": "./browser_launcher/"
     },
     {
-      "languageVersion": "2.17",
+      "languageVersion": "2.18",
       "name": "build",
       "packageUri": "lib/",
       "rootUri": "./build/"
@@ -75,7 +75,7 @@
       "rootUri": "./characters/"
     },
     {
-      "languageVersion": "2.18",
+      "languageVersion": "2.19",
       "name": "checked_yaml",
       "packageUri": "lib/",
       "rootUri": "./checked_yaml/"
@@ -93,7 +93,7 @@
       "rootUri": "./clock/"
     },
     {
-      "languageVersion": "2.17",
+      "languageVersion": "2.19",
       "name": "code_builder",
       "packageUri": "lib/",
       "rootUri": "./code_builder/"
@@ -135,7 +135,7 @@
       "rootUri": "./csslib/"
     },
     {
-      "languageVersion": "2.17",
+      "languageVersion": "2.19",
       "name": "dart_style",
       "packageUri": "lib/",
       "rootUri": "./dart_style/"
@@ -195,7 +195,7 @@
       "rootUri": "./fixnum/"
     },
     {
-      "languageVersion": "2.12",
+      "languageVersion": "2.18",
       "name": "flutter_image",
       "packageUri": "lib/",
       "rootUri": "./flutter_image/"
@@ -219,7 +219,7 @@
       "rootUri": "./frontend_server_client/"
     },
     {
-      "languageVersion": "2.12",
+      "languageVersion": "2.19",
       "name": "gcloud",
       "packageUri": "lib/",
       "rootUri": "./gcloud/"
@@ -237,7 +237,7 @@
       "rootUri": "./googleapis/"
     },
     {
-      "languageVersion": "2.13",
+      "languageVersion": "2.19",
       "name": "googleapis_auth",
       "packageUri": "lib/",
       "rootUri": "./googleapis_auth/"
@@ -249,7 +249,7 @@
       "rootUri": "./grpc/"
     },
     {
-      "languageVersion": "2.12",
+      "languageVersion": "2.17",
       "name": "html",
       "packageUri": "lib/",
       "rootUri": "./html/"
@@ -309,7 +309,7 @@
       "rootUri": "./js/"
     },
     {
-      "languageVersion": "2.18",
+      "languageVersion": "2.19",
       "name": "json_annotation",
       "packageUri": "lib/",
       "rootUri": "./json_annotation/"
@@ -363,7 +363,7 @@
       "rootUri": "./mime/"
     },
     {
-      "languageVersion": "2.17",
+      "languageVersion": "2.18",
       "name": "mockito",
       "packageUri": "lib/",
       "rootUri": "./mockito/"
@@ -471,7 +471,7 @@
       "rootUri": "./pub_semver/"
     },
     {
-      "languageVersion": "2.14",
+      "languageVersion": "2.18",
       "name": "pubspec_parse",
       "packageUri": "lib/",
       "rootUri": "./pubspec_parse/"
@@ -609,7 +609,7 @@
       "rootUri": "./test_core/"
     },
     {
-      "languageVersion": "2.14",
+      "languageVersion": "2.17",
       "name": "tuple",
       "packageUri": "lib/",
       "rootUri": "./tuple/"
@@ -645,7 +645,7 @@
       "rootUri": "./vector_math/"
     },
     {
-      "languageVersion": "2.15",
+      "languageVersion": "2.19",
       "name": "vm_service",
       "packageUri": "lib/",
       "rootUri": "./vm_service/"
diff --git a/petitparser/BUILD.gn b/petitparser/BUILD.gn
index 18806d3..3f39e7a 100644
--- a/petitparser/BUILD.gn
+++ b/petitparser/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for petitparser-5.2.0
+# This file is generated by package_importer.py for petitparser-5.3.0
 
 import("//build/dart/dart_library.gni")
 
@@ -43,6 +43,7 @@
     "src/expression/builder.dart",
     "src/expression/group.dart",
     "src/expression/result.dart",
+    "src/expression/utils.dart",
     "src/indent/indent.dart",
     "src/matcher/accept.dart",
     "src/matcher/matches.dart",
diff --git a/petitparser/CHANGELOG.md b/petitparser/CHANGELOG.md
index a153f96..6e0ea34 100644
--- a/petitparser/CHANGELOG.md
+++ b/petitparser/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 5.3.0
+
+- Maintenance release deprecating some old code in anticipation of the upcoming major release. 
+- Deprecate the old way of defining primitive parsers and move the functionality directly to `ExpressionBuilder`.
+- Deprecate `GrammarDefinition.build(Function, List<Object?>)`, use `buildFrom(Parser)` for a strongly typed parser instead.
+- Replace various uses of exception throwing with assertions, which yields code the compiler can optimize better.
+
 ## 5.2.0
 
 - Add `@useResult` to parser constructors to avoid bugs when using the old parser instance.
@@ -35,7 +42,6 @@
 ## 4.3.0
 
 * Dart 2.14 requirement.
-* Add a `where` parser, that allows to add additional constraints on parse results.
 * Add a `labeled` parser, that allows to add a debug label to the parser graph.
 * Extract `Predicate<T>` and `Callback<T>` function types to shared file.
 * Change debug functions to named arguments, and generate output events with first class objects instead of strings.
diff --git a/petitparser/README.md b/petitparser/README.md
index c716388..02127cd 100644
--- a/petitparser/README.md
+++ b/petitparser/README.md
@@ -297,37 +297,42 @@
 final builder = ExpressionBuilder<num>();
 ```
 
-Then we define the operator-groups in descending precedence. The highest precedence are the literal numbers themselves. This time we accept floating-point numbers, not just integers. In the same group we add support for the parenthesis:
+Every `ExpressionBuilder` needs to define at least one primitive type to parse. In this example these are the literal numbers. This time we accept floating-point numbers, not just integers. The mapping function converts the string input into an actual number.
 
 ```dart
-builder.group()
-  ..primitive(digit()
-      .plus()
-      .seq(char('.').seq(digit().plus()).optional())
-      .flatten()
-      .trim()
-      .map(num.parse))
-  ..wrapper(char('(').trim(), char(')').trim(), (l, a, r) => a);
+builder.primitive(digit()
+    .plus()
+    .seq(char('.').seq(digit().plus()).optional())
+    .flatten()
+    .trim()
+    .map(num.parse));
 ```
 
-Then come the normal arithmetic operators. Note, that the action blocks receive both, the terms and the parsed operator in the order they appear in the parsed input:
+Then we define the operator-groups in descending precedence. The highest precedence have parentheses. The mapping function receives both the opening parenthesis, the value, and the closing parenthesis as arguments:
 
 ```dart
-// Negation is a prefix operator
-builder.group()
-  ..prefix(char('-').trim(), (op, a) => -a);
+builder.group().wrapper(
+    char('(').trim(), char(')').trim(), (left, value, right) => value);
+```
 
-// Power is right-associative
-builder.group()
-  ..right(char('^').trim(), (a, op, b) => math.pow(a, b));
+Then come the normal arithmetic operators. We are using [cascade notation](https://dart.dev/guides/language/language-tour#cascade-notation) to define multiple operators on the same precedence-group. The mapping functions receive both, the terms and the parsed operator in the order they appear in the parsed input:
 
-// Multiplication and addition are left-associative
+```dart
+// Negation is a prefix operator.
+builder.group().prefix(char('-').trim(), (operator, value) => -value);
+
+// Power is right-associative.
+builder.group().right(
+    char('^').trim(), (left, operator, right) => math.pow(left, right));
+
+// Multiplication and addition are left-associative, multiplication has
+// higher priority than addition.
 builder.group()
-  ..left(char('*').trim(), (a, op, b) => a * b)
-  ..left(char('/').trim(), (a, op, b) => a / b);
+  ..left(char('*').trim(), (left, operator, right) => left * right)
+  ..left(char('/').trim(), (left, operator, right) => left / right);
 builder.group()
-  ..left(char('+').trim(), (a, op, b) => a + b)
-  ..left(char('-').trim(), (a, op, b) => a - b);
+  ..left(char('+').trim(), (left, operator, right) => left + right)
+  ..left(char('-').trim(), (left, operator, right) => left - right);
 ```
 
 Finally, we can build the parser:
diff --git a/petitparser/example/README.md b/petitparser/example/README.md
index 846e2e3..1efc266 100644
--- a/petitparser/example/README.md
+++ b/petitparser/example/README.md
@@ -1,7 +1,7 @@
 PetitParser Examples
 ====================
 
-For more complicated examples see the official [example repository](https://github.com/petitparser/dart-petitparser-examples) or the [demo page](https://petitparser.github.io/).
+For more elaborate examples see the official [example repository](https://github.com/petitparser/dart-petitparser-examples) or the [demo page](https://petitparser.github.io/).
  
 This directory contains the command-line calculator that is described in the introductory tutorial:
  
diff --git a/petitparser/example/calc.dart b/petitparser/example/calc.dart
index 0ded4d4..0112db7 100644
--- a/petitparser/example/calc.dart
+++ b/petitparser/example/calc.dart
@@ -6,17 +6,16 @@
 
 Parser buildParser() {
   final builder = ExpressionBuilder<num>();
-  builder.group()
-    ..primitive((pattern('+-').optional() &
-            digit().plus() &
-            (char('.') & digit().plus()).optional() &
-            (pattern('eE') & pattern('+-').optional() & digit().plus())
-                .optional())
-        .flatten('number expected')
-        .trim()
-        .map(num.parse))
-    ..wrapper(
-        char('(').trim(), char(')').trim(), (left, value, right) => value);
+  builder.primitive((pattern('+-').optional() &
+          digit().plus() &
+          (char('.') & digit().plus()).optional() &
+          (pattern('eE') & pattern('+-').optional() & digit().plus())
+              .optional())
+      .flatten('number expected')
+      .trim()
+      .map(num.parse));
+  builder.group().wrapper(
+      char('(').trim(), char(')').trim(), (left, value, right) => value);
   builder.group().prefix(char('-').trim(), (op, a) => -a);
   builder.group().right(char('^').trim(), (a, op, b) => pow(a, b));
   builder.group()
diff --git a/petitparser/lib/src/definition/grammar.dart b/petitparser/lib/src/definition/grammar.dart
index 4d00022..cef6025 100644
--- a/petitparser/lib/src/definition/grammar.dart
+++ b/petitparser/lib/src/definition/grammar.dart
@@ -71,8 +71,16 @@
   /// The optional [start] reference specifies a different starting production
   /// into the grammar. The optional [arguments] list parametrizes the called
   /// production.
+  ///
+  /// In the upcoming major release all arguments (generic and method arguments)
+  /// will be removed and the typed [start] production will be returned. Use
+  /// [buildFrom] to start at another production rule.
   @useResult
-  Parser<T> build<T>({Function? start, List<Object> arguments = const []}) {
+  @optionalTypeArgs
+  Parser<T> build<T>({
+    @Deprecated("Use `buildFrom(parser)`") Function? start,
+    @Deprecated("Use `buildFrom(parser)`") List<Object> arguments = const [],
+  }) {
     if (start != null) {
       return resolve(Function.apply(start, arguments));
     } else if (arguments.isEmpty) {
@@ -81,4 +89,8 @@
       throw StateError('Invalid arguments passed.');
     }
   }
+
+  /// Builds a composite parser starting at the specified function.
+  @useResult
+  Parser<T> buildFrom<T>(Parser<T> parser) => resolve(parser);
 }
diff --git a/petitparser/lib/src/expression/builder.dart b/petitparser/lib/src/expression/builder.dart
index ae557ed..0f40e08 100644
--- a/petitparser/lib/src/expression/builder.dart
+++ b/petitparser/lib/src/expression/builder.dart
@@ -3,51 +3,55 @@
 import '../core/parser.dart';
 import '../definition/resolve.dart';
 import '../parser/combinator/settable.dart';
-import '../parser/misc/failure.dart';
 import 'group.dart';
+import 'utils.dart';
 
 /// A builder that allows the simple definition of expression grammars with
 /// prefix, postfix, and left- and right-associative infix operators.
 ///
-/// The following code creates the empty expression builder that produces
-/// values of type [num]:
+/// The following code creates the empty expression builder producing values of
+/// type [num]:
 ///
-///     final builder = new ExpressionBuilder<num>();
+///     final builder = ExpressionBuilder<num>();
+///
+/// Every [ExpressionBuilder] needs to define at least one primitive type to
+/// parse. In this example these are the literal numbers. The mapping function
+/// converts the string input into an actual number.
+///
+///     builder.primitive(digit()
+///         .plus()
+///         .seq(char('.').seq(digit().plus()).optional())
+///         .flatten()
+///         .trim()
+///         .map(num.parse));
 ///
 /// Then we define the operator-groups in descending precedence. The highest
-/// precedence have the literal numbers themselves:
+/// precedence have parentheses. The mapping function receives both the opening
+/// parenthesis, the value, and the closing parenthesis as arguments:
 ///
+///     builder.group().wrapper(
+///         char('(').trim(), char(')').trim(), (left, value, right) => value);
+///
+/// Then come the normal arithmetic operators. We are using [cascade
+/// notation](https://dart.dev/guides/language/language-tour#cascade-notation)
+/// to define multiple operators on the same precedence-group. The mapping
+/// functions receive both, the terms and the parsed operator in the order they
+/// appear in the parsed input:
+///
+///     // Negation is a prefix operator.
+///     builder.group().prefix(char('-').trim(), (operator, value) => -value);
+///
+///     // Power is right-associative.
+///     builder.group().right(char('^').trim(), (left, operator, right) => math.pow(left, right));
+///
+///     // Multiplication and addition are left-associative, multiplication has
+///     // higher priority than addition.
 ///     builder.group()
-///       ..primitive(digit().plus()
-///         .seq(char('.').seq(digit().plus()).optional())
-///         .flatten().trim().map(num.parse));
-///
-/// If we want to support parenthesis we can add a wrapper. The callback
-/// specifies how the value is extracted from the parse result:
-///
-///     build.group()
-///       ..wrapper(char('(').trim(), char(')').trim(),
-///           (left, value, right) => value);
-///
-/// Then come the normal arithmetic operators. Note, that the callback blocks
-/// receive both, the values as well as the parsed operator in the order they
-/// appear in the parse input.
-///
-///     // negation is a prefix operator
+///       ..left(char('*').trim(), (left, operator, right) => left * right)
+///       ..left(char('/').trim(), (left, operator, right) => left / right);
 ///     builder.group()
-///       ..prefix(char('-').trim(), (op, a) => -a);
-///
-///     // power is right-associative
-///     builder.group()
-///       ..right(char('^').trim(), (a, op, b) => math.pow(a, b));
-///
-///     // multiplication and addition is left-associative
-///     builder.group()
-///       ..left(char('*').trim(), (a, op, b) => a * b)
-///       ..left(char('/').trim(), (a, op, b) => a / b);
-///     builder.group()
-///       ..left(char('+').trim(), (a, op, b) => a + b)
-///       ..left(char('-').trim(), (a, op, b) => a - b);
+///       ..left(char('+').trim(), (left, operator, right) => left + right)
+///       ..left(char('-').trim(), (left, operator, right) => left - right);
 ///
 /// Finally we can build the parser:
 ///
@@ -63,9 +67,13 @@
 ///     parser.parse('2^2^3');   // 256
 ///
 class ExpressionBuilder<T> {
+  final List<Parser<T>> _primitives = [];
   final List<ExpressionGroup<T>> _groups = [];
   final SettableParser<T> _loopback = undefined();
 
+  /// Defines a new primitive, value, or literal) [parser].
+  void primitive(Parser<T> parser) => _primitives.add(parser);
+
   /// Creates a new group of operators that share the same priority.
   @useResult
   ExpressionGroup<T> group() {
@@ -77,9 +85,14 @@
   /// Builds the expression parser.
   @useResult
   Parser<T> build() {
+    final primitives = <Parser<T>>[
+      ..._primitives,
+      ..._groups.expand((group) => group.primitives),
+    ];
+    assert(primitives.isNotEmpty, 'At least one primitive parser expected');
     final parser = _groups.fold<Parser<T>>(
-      failure('Highest priority group should define a primitive parser.'),
-      (a, b) => b.build(a),
+      buildChoice(primitives),
+      (parser, group) => group.build(parser),
     );
     _loopback.set(parser);
     return resolve(parser);
diff --git a/petitparser/lib/src/expression/group.dart b/petitparser/lib/src/expression/group.dart
index ff36131..bb8c1bf 100644
--- a/petitparser/lib/src/expression/group.dart
+++ b/petitparser/lib/src/expression/group.dart
@@ -2,11 +2,11 @@
 
 import '../core/parser.dart';
 import '../parser/action/map.dart';
-import '../parser/combinator/choice.dart';
 import '../parser/combinator/sequence.dart';
 import '../parser/repeater/possessive.dart';
 import '../parser/repeater/separated.dart';
 import 'result.dart';
+import 'utils.dart';
 
 /// Models a group of operators of the same precedence.
 class ExpressionGroup<T> {
@@ -17,12 +17,12 @@
   final Parser<T> _loopback;
 
   /// Defines a new primitive or literal [parser].
-  void primitive(Parser<T> parser) => _primitive.add(parser);
+  @Deprecated('Define primitive parsers directly on the builder using '
+      '`ExpressionBuilder.primitive`')
+  void primitive(Parser<T> parser) => primitives.add(parser);
 
-  Parser<T> _buildPrimitive(Parser<T> inner) =>
-      _primitive.isEmpty ? inner : _buildChoice(_primitive);
-
-  final List<Parser<T>> _primitive = [];
+  @internal
+  final List<Parser<T>> primitives = [];
 
   /// Defines a new wrapper using [left] and [right] parsers, that are typically
   /// used for parenthesis. Evaluates the [callback] with the parsed `left`
@@ -31,8 +31,7 @@
           T Function(L left, T value, R right) callback) =>
       _wrapper.add(seq3(left, _loopback, right).map3(callback));
 
-  Parser<T> _buildWrapper(Parser<T> inner) =>
-      _buildChoice([..._wrapper, inner]);
+  Parser<T> _buildWrapper(Parser<T> inner) => buildChoice([..._wrapper, inner]);
 
   final List<Parser<T>> _wrapper = [];
 
@@ -44,7 +43,7 @@
 
   Parser<T> _buildPrefix(Parser<T> inner) => _prefix.isEmpty
       ? inner
-      : seq2(_buildChoice(_prefix).star(), inner).map2((prefix, value) =>
+      : seq2(buildChoice(_prefix).star(), inner).map2((prefix, value) =>
           prefix.reversed.fold(value, (each, result) => result.call(each)));
 
   final List<Parser<ExpressionResultPrefix<T, void>>> _prefix = [];
@@ -57,7 +56,7 @@
 
   Parser<T> _buildPostfix(Parser<T> inner) => _postfix.isEmpty
       ? inner
-      : seq2(inner, _buildChoice(_postfix).star()).map2((value, postfix) =>
+      : seq2(inner, buildChoice(_postfix).star()).map2((value, postfix) =>
           postfix.fold(value, (each, result) => result.call(each)));
 
   final List<Parser<ExpressionResultPostfix<T, void>>> _postfix = [];
@@ -71,7 +70,7 @@
 
   Parser<T> _buildRight(Parser<T> inner) => _right.isEmpty
       ? inner
-      : inner.plusSeparated(_buildChoice(_right)).map((sequence) => sequence
+      : inner.plusSeparated(buildChoice(_right)).map((sequence) => sequence
           .foldRight((left, result, right) => result.call(left, right)));
 
   final List<Parser<ExpressionResultInfix<T, void>>> _right = [];
@@ -85,17 +84,13 @@
 
   Parser<T> _buildLeft(Parser<T> inner) => _left.isEmpty
       ? inner
-      : inner.plusSeparated(_buildChoice(_left)).map((sequence) =>
+      : inner.plusSeparated(buildChoice(_left)).map((sequence) =>
           sequence.foldLeft((left, result, right) => result.call(left, right)));
 
   final List<Parser<ExpressionResultInfix<T, void>>> _left = [];
 
   // Internal helper to build the group of parsers.
   @internal
-  Parser<T> build(Parser<T> inner) => _buildLeft(_buildRight(
-      _buildPostfix(_buildPrefix(_buildWrapper(_buildPrimitive(inner))))));
+  Parser<T> build(Parser<T> inner) => _buildLeft(
+      _buildRight(_buildPostfix(_buildPrefix(_buildWrapper(inner)))));
 }
-
-// Internal helper to build an optimal choice parser.
-Parser<T> _buildChoice<T>(List<Parser<T>> parsers) =>
-    parsers.length == 1 ? parsers.first : parsers.toChoiceParser();
diff --git a/petitparser/lib/src/expression/utils.dart b/petitparser/lib/src/expression/utils.dart
new file mode 100644
index 0000000..9068db9
--- /dev/null
+++ b/petitparser/lib/src/expression/utils.dart
@@ -0,0 +1,6 @@
+import '../core/parser.dart';
+import '../parser/combinator/choice.dart';
+
+// Internal helper to build an optimal choice parser.
+Parser<R> buildChoice<R>(List<Parser<R>> parsers) =>
+    parsers.length == 1 ? parsers.first : parsers.toChoiceParser();
diff --git a/petitparser/lib/src/parser/action/flatten.dart b/petitparser/lib/src/parser/action/flatten.dart
index f657e0c..5628aed 100644
--- a/petitparser/lib/src/parser/action/flatten.dart
+++ b/petitparser/lib/src/parser/action/flatten.dart
@@ -5,7 +5,7 @@
 import '../../core/parser.dart';
 import '../combinator/delegate.dart';
 
-extension FlattenParserExtension<T> on Parser<T> {
+extension FlattenParserExtension<R> on Parser<R> {
   /// Returns a parser that discards the result of the receiver and answers
   /// the sub-string its delegate consumes.
   ///
@@ -17,12 +17,12 @@
   /// for the input `'abc'`. In contrast, the parser `letter().plus()` would
   /// return `['a', 'b', 'c']` for the same input instead.
   @useResult
-  Parser<String> flatten([String? message]) => FlattenParser<T>(this, message);
+  Parser<String> flatten([String? message]) => FlattenParser<R>(this, message);
 }
 
 /// A parser that discards the result of the delegate and answers the
 /// sub-string its delegate consumes.
-class FlattenParser<T> extends DelegateParser<T, String> {
+class FlattenParser<R> extends DelegateParser<R, String> {
   FlattenParser(super.delegate, [this.message]);
 
   /// Error message to indicate parse failures with.
@@ -54,9 +54,9 @@
       delegate.fastParseOn(buffer, position);
 
   @override
-  bool hasEqualProperties(FlattenParser<T> other) =>
+  bool hasEqualProperties(FlattenParser<R> other) =>
       super.hasEqualProperties(other) && message == other.message;
 
   @override
-  FlattenParser<T> copy() => FlattenParser<T>(delegate, message);
+  FlattenParser<R> copy() => FlattenParser<R>(delegate, message);
 }
diff --git a/petitparser/lib/src/parser/action/permute.dart b/petitparser/lib/src/parser/action/permute.dart
index 8e0e37e..dfa9612 100644
--- a/petitparser/lib/src/parser/action/permute.dart
+++ b/petitparser/lib/src/parser/action/permute.dart
@@ -5,7 +5,7 @@
 import '../../core/parser.dart';
 import '../combinator/delegate.dart';
 
-extension PermuteParserExtension<T> on Parser<List<T>> {
+extension PermuteParserExtension<R> on Parser<List<R>> {
   /// Returns a parser that transforms a successful parse result by returning
   /// the permuted elements at [indexes] of a list. Negative indexes can be
   /// used to access the elements from the back of the list.
@@ -14,7 +14,7 @@
   /// first and last letter parsed. For the input `'abc'` it returns
   /// `['a', 'c']`.
   @useResult
-  Parser<List<T>> permute(List<int> indexes) => PermuteParser<T>(this, indexes);
+  Parser<List<R>> permute(List<int> indexes) => PermuteParser<R>(this, indexes);
 }
 
 /// A parser that performs a transformation with a given function on the
diff --git a/petitparser/lib/src/parser/action/pick.dart b/petitparser/lib/src/parser/action/pick.dart
index ad8bd01..b045bcc 100644
--- a/petitparser/lib/src/parser/action/pick.dart
+++ b/petitparser/lib/src/parser/action/pick.dart
@@ -5,7 +5,7 @@
 import '../../core/parser.dart';
 import '../combinator/delegate.dart';
 
-extension PickParserExtension<T> on Parser<List<T>> {
+extension PickParserExtension<R> on Parser<List<R>> {
   /// Returns a parser that transforms a successful parse result by returning
   /// the element at [index] of a list. A negative index can be used to access
   /// the elements from the back of the list.
@@ -13,7 +13,7 @@
   /// For example, the parser `letter().star().pick(-1)` returns the last
   /// letter parsed. For the input `'abc'` it returns `'c'`.
   @useResult
-  Parser<T> pick(int index) => PickParser<T>(this, index);
+  Parser<R> pick(int index) => PickParser<R>(this, index);
 }
 
 /// A parser that performs a transformation with a given function on the
diff --git a/petitparser/lib/src/parser/action/token.dart b/petitparser/lib/src/parser/action/token.dart
index 970506a..6d83fea 100644
--- a/petitparser/lib/src/parser/action/token.dart
+++ b/petitparser/lib/src/parser/action/token.dart
@@ -6,7 +6,7 @@
 import '../../core/token.dart';
 import '../combinator/delegate.dart';
 
-extension TokenParserExtension<T> on Parser<T> {
+extension TokenParserExtension<R> on Parser<R> {
   /// Returns a parser that returns a [Token]. The token carries the parsed
   /// value of the receiver [Token.value], as well as the consumed input
   /// [Token.input] from [Token.start] to [Token.stop] of the input being
@@ -15,7 +15,7 @@
   /// For example, the parser `letter().plus().token()` returns the token
   /// `Token[start: 0, stop: 3, value: abc]` for the input `'abc'`.
   @useResult
-  Parser<Token<T>> token() => TokenParser<T>(this);
+  Parser<Token<R>> token() => TokenParser<R>(this);
 }
 
 /// A parser that creates a token of the result its delegate parses.
diff --git a/petitparser/lib/src/parser/action/trimming.dart b/petitparser/lib/src/parser/action/trimming.dart
index dd41079..4176932 100644
--- a/petitparser/lib/src/parser/action/trimming.dart
+++ b/petitparser/lib/src/parser/action/trimming.dart
@@ -8,7 +8,7 @@
 import '../combinator/delegate.dart';
 import '../utils/sequential.dart';
 
-extension TrimmingParserExtension<T> on Parser<T> {
+extension TrimmingParserExtension<R> on Parser<R> {
   /// Returns a parser that consumes input before and after the receiver,
   /// discards the excess input and only returns the result of the receiver.
   /// The optional arguments are parsers that consume the excess input. By
@@ -18,8 +18,8 @@
   /// For example, the parser `letter().plus().trim()` returns `['a', 'b']`
   /// for the input `' ab\n'` and consumes the complete input string.
   @useResult
-  Parser<T> trim([Parser<void>? left, Parser<void>? right]) =>
-      TrimmingParser<T>(this, left ??= whitespace(), right ??= left);
+  Parser<R> trim([Parser<void>? left, Parser<void>? right]) =>
+      TrimmingParser<R>(this, left ??= whitespace(), right ??= left);
 }
 
 /// A parser that silently consumes input of another parser around
diff --git a/petitparser/lib/src/parser/action/where.dart b/petitparser/lib/src/parser/action/where.dart
index fa3fb6d..3fef6cd 100644
--- a/petitparser/lib/src/parser/action/where.dart
+++ b/petitparser/lib/src/parser/action/where.dart
@@ -8,7 +8,7 @@
 import '../../shared/types.dart';
 import '../combinator/delegate.dart';
 
-extension WhereParserExtension<T> on Parser<T> {
+extension WhereParserExtension<R> on Parser<R> {
   /// Returns a parser that evaluates the [predicate] with the successful
   /// parse result. If the predicate returns `true` the parser proceeds with
   /// the parse result, otherwise a parse failure is created using the
@@ -35,15 +35,15 @@
   ///     parser.parse('ab');   // ==> Failure: characters do not match
   ///
   @useResult
-  Parser<T> where(
-    Predicate<T> predicate, {
-    FailureFactory<T>? failureFactory,
+  Parser<R> where(
+    Predicate<R> predicate, {
+    FailureFactory<R>? failureFactory,
     @Deprecated('Use `failureFactory` instead')
-        Callback<T, String>? failureMessage,
+        Callback<R, String>? failureMessage,
     @Deprecated('Use `failureFactory` instead')
-        Callback<T, int>? failurePosition,
+        Callback<R, int>? failurePosition,
   }) =>
-      WhereParser<T>(
+      WhereParser<R>(
           this,
           predicate,
           failureFactory ??
@@ -56,29 +56,29 @@
                       context.failure('unexpected "${success.value}"')));
 }
 
-typedef FailureFactory<T> = Failure<T> Function(
-    Context context, Success<T> success);
+typedef FailureFactory<R> = Failure<R> Function(
+    Context context, Success<R> success);
 
-class WhereParser<T> extends DelegateParser<T, T> {
+class WhereParser<R> extends DelegateParser<R, R> {
   WhereParser(super.parser, this.predicate, this.failureFactory);
 
-  final Predicate<T> predicate;
-  final FailureFactory<T> failureFactory;
+  final Predicate<R> predicate;
+  final FailureFactory<R> failureFactory;
 
   @override
-  Result<T> parseOn(Context context) {
+  Result<R> parseOn(Context context) {
     final result = delegate.parseOn(context);
-    if (result is Success<T> && !predicate(result.value)) {
+    if (result is Success<R> && !predicate(result.value)) {
       return failureFactory(context, result);
     }
     return result;
   }
 
   @override
-  Parser<T> copy() => WhereParser<T>(delegate, predicate, failureFactory);
+  Parser<R> copy() => WhereParser<R>(delegate, predicate, failureFactory);
 
   @override
-  bool hasEqualProperties(WhereParser<T> other) =>
+  bool hasEqualProperties(WhereParser<R> other) =>
       super.hasEqualProperties(other) &&
       predicate == other.predicate &&
       failureFactory == other.failureFactory;
diff --git a/petitparser/lib/src/parser/character/code.dart b/petitparser/lib/src/parser/character/code.dart
index f837070..a2287b9 100644
--- a/petitparser/lib/src/parser/character/code.dart
+++ b/petitparser/lib/src/parser/character/code.dart
@@ -1,10 +1,7 @@
 /// Converts an object to a character code.
-int toCharCode(String element) {
-  final value = element.toString();
-  if (value.length != 1) {
-    throw ArgumentError('"$value" is not a character');
-  }
-  return value.codeUnitAt(0);
+int toCharCode(String char) {
+  assert(char.length == 1, '"$char" is not a valid character');
+  return char.codeUnitAt(0);
 }
 
 /// Converts a character to a readable string.
diff --git a/petitparser/lib/src/parser/character/lookup.dart b/petitparser/lib/src/parser/character/lookup.dart
index 5377938..7724695 100644
--- a/petitparser/lib/src/parser/character/lookup.dart
+++ b/petitparser/lib/src/parser/character/lookup.dart
@@ -1,5 +1,6 @@
 import 'dart:typed_data';
 
+import '../../shared/annotations.dart';
 import 'predicate.dart';
 import 'range.dart';
 
@@ -26,6 +27,8 @@
   bool test(int value) =>
       start <= value && value <= stop && _testBit(value - start);
 
+  @inlineJs
+  @inlineVm
   bool _testBit(int value) =>
       (bits[value >> shift] & mask[value & offset]) != 0;
 
diff --git a/petitparser/lib/src/parser/character/range.dart b/petitparser/lib/src/parser/character/range.dart
index e7f8cc3..7634f0c 100644
--- a/petitparser/lib/src/parser/character/range.dart
+++ b/petitparser/lib/src/parser/character/range.dart
@@ -15,11 +15,8 @@
             '[${toReadableString(start)}-${toReadableString(stop)}] expected');
 
 class RangeCharPredicate implements CharacterPredicate {
-  RangeCharPredicate(this.start, this.stop) {
-    if (start > stop) {
-      throw ArgumentError('Invalid range: $start-$stop');
-    }
-  }
+  RangeCharPredicate(this.start, this.stop)
+      : assert(start <= stop, 'Invalid range character range: $start-$stop');
 
   final int start;
   final int stop;
diff --git a/petitparser/lib/src/parser/combinator/and.dart b/petitparser/lib/src/parser/combinator/and.dart
index 3f1d53f..f781fd9 100644
--- a/petitparser/lib/src/parser/combinator/and.dart
+++ b/petitparser/lib/src/parser/combinator/and.dart
@@ -5,7 +5,7 @@
 import '../../core/parser.dart';
 import 'delegate.dart';
 
-extension AndParserExtension<T> on Parser<T> {
+extension AndParserExtension<R> on Parser<R> {
   /// Returns a parser (logical and-predicate) that succeeds whenever the
   /// receiver does, but never consumes input.
   ///
@@ -14,7 +14,7 @@
   /// does not consume accepted input, the parser `identifier` is given the
   /// ability to process the complete identifier.
   @useResult
-  Parser<T> and() => AndParser<T>(this);
+  Parser<R> and() => AndParser<R>(this);
 }
 
 /// The and-predicate, a parser that succeeds whenever its delegate does, but
diff --git a/petitparser/lib/src/parser/combinator/choice.dart b/petitparser/lib/src/parser/combinator/choice.dart
index 51e5bf0..07966d7 100644
--- a/petitparser/lib/src/parser/combinator/choice.dart
+++ b/petitparser/lib/src/parser/combinator/choice.dart
@@ -42,35 +42,31 @@
   ChoiceParser operator |(Parser other) => or(other);
 }
 
-extension ChoiceIterableExtension<T> on Iterable<Parser<T>> {
+extension ChoiceIterableExtension<R> on Iterable<Parser<R>> {
   /// Converts the parser in this iterable to a choice of parsers.
-  ChoiceParser<T> toChoiceParser({FailureJoiner<T>? failureJoiner}) =>
-      ChoiceParser<T>(this, failureJoiner: failureJoiner);
+  ChoiceParser<R> toChoiceParser({FailureJoiner<R>? failureJoiner}) =>
+      ChoiceParser<R>(this, failureJoiner: failureJoiner);
 }
 
 /// A parser that uses the first parser that succeeds.
-class ChoiceParser<T> extends ListParser<T, T> {
-  ChoiceParser(Iterable<Parser<T>> children, {FailureJoiner<T>? failureJoiner})
-      : failureJoiner = failureJoiner ?? selectLast,
-        super(children) {
-    if (children.isEmpty) {
-      throw ArgumentError('Choice parser cannot be empty.');
-    }
-  }
+class ChoiceParser<R> extends ListParser<R, R> {
+  ChoiceParser(super.children, {FailureJoiner<R>? failureJoiner})
+      : assert(children.isNotEmpty, 'Choice parser cannot be empty'),
+        failureJoiner = failureJoiner ?? selectLast;
 
   /// Strategy to join multiple parse errors.
-  final FailureJoiner<T> failureJoiner;
+  final FailureJoiner<R> failureJoiner;
 
   /// Switches the failure joining strategy.
-  ChoiceParser<T> withFailureJoiner(FailureJoiner<T> failureJoiner) =>
-      ChoiceParser<T>(children, failureJoiner: failureJoiner);
+  ChoiceParser<R> withFailureJoiner(FailureJoiner<R> failureJoiner) =>
+      ChoiceParser<R>(children, failureJoiner: failureJoiner);
 
   @override
-  Result<T> parseOn(Context context) {
-    Failure<T>? failure;
+  Result<R> parseOn(Context context) {
+    Failure<R>? failure;
     for (var i = 0; i < children.length; i++) {
       final result = children[i].parseOn(context);
-      if (result is Failure<T>) {
+      if (result is Failure<R>) {
         failure = failure == null ? result : failureJoiner(failure, result);
       } else {
         return result;
@@ -92,10 +88,10 @@
   }
 
   @override
-  bool hasEqualProperties(ChoiceParser<T> other) =>
+  bool hasEqualProperties(ChoiceParser<R> other) =>
       super.hasEqualProperties(other) && failureJoiner == other.failureJoiner;
 
   @override
-  ChoiceParser<T> copy() =>
-      ChoiceParser<T>(children, failureJoiner: failureJoiner);
+  ChoiceParser<R> copy() =>
+      ChoiceParser<R>(children, failureJoiner: failureJoiner);
 }
diff --git a/petitparser/lib/src/parser/combinator/not.dart b/petitparser/lib/src/parser/combinator/not.dart
index 48a0174..9dd2b9d 100644
--- a/petitparser/lib/src/parser/combinator/not.dart
+++ b/petitparser/lib/src/parser/combinator/not.dart
@@ -8,7 +8,7 @@
 import 'delegate.dart';
 import 'sequence.dart';
 
-extension NotParserExtension<T> on Parser<T> {
+extension NotParserExtension<R> on Parser<R> {
   /// Returns a parser (logical not-predicate) that succeeds with the [Failure]
   /// whenever the receiver fails, but never consumes input.
   ///
@@ -18,7 +18,7 @@
   /// complete parser fails. Otherwise the parser `identifier` is given the
   /// ability to process the complete identifier.
   @useResult
-  Parser<Failure<T>> not([String message = 'success not expected']) =>
+  Parser<Failure<R>> not([String message = 'success not expected']) =>
       NotParser(this, message);
 
   /// Returns a parser that consumes any input token (character), but the
diff --git a/petitparser/lib/src/parser/combinator/optional.dart b/petitparser/lib/src/parser/combinator/optional.dart
index 18bca6d..810d245 100644
--- a/petitparser/lib/src/parser/combinator/optional.dart
+++ b/petitparser/lib/src/parser/combinator/optional.dart
@@ -5,7 +5,7 @@
 import '../../core/parser.dart';
 import 'delegate.dart';
 
-extension OptionalParserExtension<T> on Parser<T> {
+extension OptionalParserExtension<R> on Parser<R> {
   /// Returns new parser that accepts the receiver, if possible. The resulting
   /// parser returns the result of the receiver, or `null` if not applicable.
   ///
@@ -13,7 +13,7 @@
   /// and returns that letter. When given something else the parser succeeds as
   /// well, does not consume anything and returns `null`.
   @useResult
-  Parser<T?> optional() => OptionalParser<T?>(this, null);
+  Parser<R?> optional() => OptionalParser<R?>(this, null);
 
   /// Returns new parser that accepts the receiver, if possible. The resulting
   /// parser returns the result of the receiver, or [value] if not applicable.
@@ -22,7 +22,7 @@
   /// input and returns that letter. When given something else the parser
   /// succeeds as well, does not consume anything and returns `'!'`.
   @useResult
-  Parser<T> optionalWith(T value) => OptionalParser<T>(this, value);
+  Parser<R> optionalWith(R value) => OptionalParser<R>(this, value);
 }
 
 /// A parser that optionally parsers its delegate, or answers `null`.
diff --git a/petitparser/lib/src/parser/combinator/sequence.dart b/petitparser/lib/src/parser/combinator/sequence.dart
index b6190a8..5956e86 100644
--- a/petitparser/lib/src/parser/combinator/sequence.dart
+++ b/petitparser/lib/src/parser/combinator/sequence.dart
@@ -36,20 +36,20 @@
   Parser<List> operator &(Parser other) => seq(other);
 }
 
-extension SequenceIterableExtension<T> on Iterable<Parser<T>> {
+extension SequenceIterableExtension<R> on Iterable<Parser<R>> {
   /// Converts the parser in this iterable to a sequence of parsers.
-  Parser<List<T>> toSequenceParser() => SequenceParser<T>(this);
+  Parser<List<R>> toSequenceParser() => SequenceParser<R>(this);
 }
 
 /// A parser that parses a sequence of parsers.
-class SequenceParser<T> extends ListParser<T, List<T>>
+class SequenceParser<R> extends ListParser<R, List<R>>
     implements SequentialParser {
   SequenceParser(super.children);
 
   @override
-  Result<List<T>> parseOn(Context context) {
+  Result<List<R>> parseOn(Context context) {
     var current = context;
-    final elements = <T>[];
+    final elements = <R>[];
     for (var i = 0; i < children.length; i++) {
       final result = children[i].parseOn(current);
       if (result.isFailure) {
@@ -73,5 +73,5 @@
   }
 
   @override
-  SequenceParser<T> copy() => SequenceParser<T>(children);
+  SequenceParser<R> copy() => SequenceParser<R>(children);
 }
diff --git a/petitparser/lib/src/parser/combinator/settable.dart b/petitparser/lib/src/parser/combinator/settable.dart
index 865f407..59cb730 100644
--- a/petitparser/lib/src/parser/combinator/settable.dart
+++ b/petitparser/lib/src/parser/combinator/settable.dart
@@ -7,7 +7,7 @@
 import '../misc/failure.dart';
 import '../utils/resolvable.dart';
 
-extension SettableParserExtension<T> on Parser<T> {
+extension SettableParserExtension<R> on Parser<R> {
   /// Returns a parser that points to the receiver, but can be changed to point
   /// to something else at a later point in time.
   ///
@@ -15,7 +15,7 @@
   /// as `letter()`, but it can be replaced with another parser using
   /// [SettableParser.set].
   @useResult
-  SettableParser<T> settable() => SettableParser<T>(this);
+  SettableParser<R> settable() => SettableParser<R>(this);
 }
 
 /// Returns a parser that is not defined, but that can be set at a later
diff --git a/petitparser/lib/src/parser/combinator/skip.dart b/petitparser/lib/src/parser/combinator/skip.dart
index fa156bb..b187123 100644
--- a/petitparser/lib/src/parser/combinator/skip.dart
+++ b/petitparser/lib/src/parser/combinator/skip.dart
@@ -3,7 +3,7 @@
 import '../../core/parser.dart';
 import 'sequence.dart';
 
-extension SkipParserExtension<T> on Parser<T> {
+extension SkipParserExtension<R> on Parser<R> {
   /// Returns a parser that consumes input [before] and [after] the receiver,
   /// but discards the parse results of [before] and [after] and only returns
   /// the result of the receiver.
@@ -11,7 +11,7 @@
   /// For example, the parser `digit().skip(char('['), char(']'))`
   /// returns `'3'` for the input `'[3]'`.
   @useResult
-  Parser<T> skip({Parser<void>? before, Parser<void>? after}) => before == null
+  Parser<R> skip({Parser<void>? before, Parser<void>? after}) => before == null
       ? after == null
           ? this
           : seq2(this, after).map2((value, _) => value)
diff --git a/petitparser/lib/src/parser/misc/eof.dart b/petitparser/lib/src/parser/misc/eof.dart
index 523e9de..3655eb8 100644
--- a/petitparser/lib/src/parser/misc/eof.dart
+++ b/petitparser/lib/src/parser/misc/eof.dart
@@ -5,7 +5,7 @@
 import '../../core/parser.dart';
 import '../combinator/sequence.dart';
 
-extension EndOfInputParserExtension<T> on Parser<T> {
+extension EndOfInputParserExtension<R> on Parser<R> {
   /// Returns a parser that succeeds only if the receiver consumes the complete
   /// input, otherwise return a failure with the optional [message].
   ///
@@ -13,7 +13,7 @@
   /// and fails on `'ab'`. In contrast the parser `letter()` alone would
   /// succeed on both inputs, but not consume everything for the second input.
   @useResult
-  Parser<T> end([String message = 'end of input expected']) =>
+  Parser<R> end([String message = 'end of input expected']) =>
       seq2(this, endOfInput(message)).map2((value, _) => value);
 }
 
diff --git a/petitparser/lib/src/parser/repeater/greedy.dart b/petitparser/lib/src/parser/repeater/greedy.dart
index 7d8716c..103ad61 100644
--- a/petitparser/lib/src/parser/repeater/greedy.dart
+++ b/petitparser/lib/src/parser/repeater/greedy.dart
@@ -8,7 +8,7 @@
 import 'possessive.dart';
 import 'unbounded.dart';
 
-extension GreedyRepeatingParserExtension<T> on Parser<T> {
+extension GreedyRepeatingParserExtension<R> on Parser<R> {
   /// Returns a parser that parses the receiver zero or more times until it
   /// reaches a [limit]. This is a greedy non-blind implementation of the
   /// [star] operator. The [limit] is not consumed.
@@ -19,7 +19,7 @@
   /// See [starLazy] for the lazy, more efficient, and generally preferred
   /// variation of this combinator.
   @useResult
-  Parser<List<T>> starGreedy(Parser<void> limit) =>
+  Parser<List<R>> starGreedy(Parser<void> limit) =>
       repeatGreedy(limit, 0, unbounded);
 
   /// Returns a parser that parses the receiver one or more times until it
@@ -32,7 +32,7 @@
   /// See [plusLazy] for the lazy, more efficient, and generally preferred
   /// variation of this combinator.
   @useResult
-  Parser<List<T>> plusGreedy(Parser<void> limit) =>
+  Parser<List<R>> plusGreedy(Parser<void> limit) =>
       repeatGreedy(limit, 1, unbounded);
 
   /// Returns a parser that parses the receiver at least [min] and at most [max]
@@ -42,8 +42,8 @@
   /// This is the more generic variation of the [starGreedy] and [plusGreedy]
   /// combinators.
   @useResult
-  Parser<List<T>> repeatGreedy(Parser<void> limit, int min, int max) =>
-      GreedyRepeatingParser<T>(this, limit, min, max);
+  Parser<List<R>> repeatGreedy(Parser<void> limit, int min, int max) =>
+      GreedyRepeatingParser<R>(this, limit, min, max);
 }
 
 /// A greedy repeating parser, commonly seen in regular expression
diff --git a/petitparser/lib/src/parser/repeater/lazy.dart b/petitparser/lib/src/parser/repeater/lazy.dart
index c274e73..8c17cd8 100644
--- a/petitparser/lib/src/parser/repeater/lazy.dart
+++ b/petitparser/lib/src/parser/repeater/lazy.dart
@@ -8,7 +8,7 @@
 import 'possessive.dart';
 import 'unbounded.dart';
 
-extension LazyRepeatingParserExtension<T> on Parser<T> {
+extension LazyRepeatingParserExtension<R> on Parser<R> {
   /// Returns a parser that parses the receiver zero or more times until it
   /// reaches a [limit]. This is a lazy non-blind implementation of the [star]
   /// operator. The [limit] is not consumed.
@@ -19,7 +19,7 @@
   /// See [starGreedy] for the greedy and less efficient variation of
   /// this combinator.
   @useResult
-  Parser<List<T>> starLazy(Parser<void> limit) =>
+  Parser<List<R>> starLazy(Parser<void> limit) =>
       repeatLazy(limit, 0, unbounded);
 
   /// Returns a parser that parses the receiver one or more times until it
@@ -32,7 +32,7 @@
   /// See [plusGreedy] for the greedy and less efficient variation of
   /// this combinator.
   @useResult
-  Parser<List<T>> plusLazy(Parser<void> limit) =>
+  Parser<List<R>> plusLazy(Parser<void> limit) =>
       repeatLazy(limit, 1, unbounded);
 
   /// Returns a parser that parses the receiver at least [min] and at most [max]
@@ -42,8 +42,8 @@
   /// This is the more generic variation of the [starLazy] and [plusLazy]
   /// combinators.
   @useResult
-  Parser<List<T>> repeatLazy(Parser<void> limit, int min, int max) =>
-      LazyRepeatingParser<T>(this, limit, min, max);
+  Parser<List<R>> repeatLazy(Parser<void> limit, int min, int max) =>
+      LazyRepeatingParser<R>(this, limit, min, max);
 }
 
 /// A lazy repeating parser, commonly seen in regular expression
diff --git a/petitparser/lib/src/parser/repeater/possessive.dart b/petitparser/lib/src/parser/repeater/possessive.dart
index e792853..876cbf6 100644
--- a/petitparser/lib/src/parser/repeater/possessive.dart
+++ b/petitparser/lib/src/parser/repeater/possessive.dart
@@ -6,7 +6,7 @@
 import 'repeating.dart';
 import 'unbounded.dart';
 
-extension PossessiveRepeatingParserExtension<T> on Parser<T> {
+extension PossessiveRepeatingParserExtension<R> on Parser<R> {
   /// Returns a parser that accepts the receiver zero or more times. The
   /// resulting parser returns a list of the parse results of the receiver.
   ///
@@ -17,7 +17,7 @@
   /// any sequence of letters and returns a possibly empty list of the parsed
   /// letters.
   @useResult
-  Parser<List<T>> star() => repeat(0, unbounded);
+  Parser<List<R>> star() => repeat(0, unbounded);
 
   /// Returns a parser that accepts the receiver one or more times. The
   /// resulting parser returns a list of the parse results of the receiver.
@@ -28,7 +28,7 @@
   /// For example, the parser `letter().plus()` accepts any sequence of
   /// letters and returns a list of the parsed letters.
   @useResult
-  Parser<List<T>> plus() => repeat(1, unbounded);
+  Parser<List<R>> plus() => repeat(1, unbounded);
 
   /// Returns a parser that accepts the receiver exactly [count] times. The
   /// resulting parser returns a list of the parse results of the receiver.
@@ -36,7 +36,7 @@
   /// For example, the parser `letter().times(2)` accepts two letters and
   /// returns a list of the two parsed letters.
   @useResult
-  Parser<List<T>> times(int count) => repeat(count, count);
+  Parser<List<R>> times(int count) => repeat(count, count);
 
   /// Returns a parser that accepts the receiver between [min] and [max] times.
   /// The resulting parser returns a list of the parse results of the receiver.
@@ -47,8 +47,8 @@
   /// For example, the parser `letter().repeat(2, 4)` accepts a sequence of
   /// two, three, or four letters and returns the accepted letters as a list.
   @useResult
-  Parser<List<T>> repeat(int min, [int? max]) =>
-      PossessiveRepeatingParser<T>(this, min, max ?? min);
+  Parser<List<R>> repeat(int min, [int? max]) =>
+      PossessiveRepeatingParser<R>(this, min, max ?? min);
 }
 
 /// A greedy parser that repeatedly parses between 'min' and 'max' instances of
diff --git a/petitparser/lib/src/parser/repeater/repeating.dart b/petitparser/lib/src/parser/repeater/repeating.dart
index 5b15221..df0df2c 100644
--- a/petitparser/lib/src/parser/repeater/repeating.dart
+++ b/petitparser/lib/src/parser/repeater/repeating.dart
@@ -4,16 +4,9 @@
 /// An abstract parser that repeatedly parses between 'min' and 'max' instances
 /// of its delegate.
 abstract class RepeatingParser<T, R> extends DelegateParser<T, R> {
-  RepeatingParser(super.parser, this.min, this.max) {
-    if (min < 0) {
-      throw ArgumentError(
-          'Minimum repetitions must be positive, but got $min.');
-    }
-    if (max < min) {
-      throw ArgumentError(
-          'Maximum repetitions must be larger than $min, but got $max.');
-    }
-  }
+  RepeatingParser(super.parser, this.min, this.max)
+      : assert(0 <= min, 'min must be at least 0, but got $min'),
+        assert(min <= max, 'max must be at least $min, but got $max');
 
   /// The minimum amount of repetitions.
   final int min;
diff --git a/petitparser/lib/src/parser/utils/failure_joiner.dart b/petitparser/lib/src/parser/utils/failure_joiner.dart
index e3d2f3d..60782de 100644
--- a/petitparser/lib/src/parser/utils/failure_joiner.dart
+++ b/petitparser/lib/src/parser/utils/failure_joiner.dart
@@ -1,25 +1,25 @@
 import '../../context/failure.dart';
 
 /// Function definition that joins parse [Failure] instances.
-typedef FailureJoiner<T> = Failure<T> Function(
-    Failure<T> first, Failure<T> second);
+typedef FailureJoiner<R> = Failure<R> Function(
+    Failure<R> first, Failure<R> second);
 
 /// Reports the first parse failure observed.
-Failure<T> selectFirst<T>(Failure<T> first, Failure<T> second) => first;
+Failure<R> selectFirst<R>(Failure<R> first, Failure<R> second) => first;
 
 /// Reports the last parse failure observed (default).
-Failure<T> selectLast<T>(Failure<T> first, Failure<T> second) => second;
+Failure<R> selectLast<R>(Failure<R> first, Failure<R> second) => second;
 
 /// Reports the parser failure farthest down in the input string, preferring
 /// later failures over earlier ones.
-Failure<T> selectFarthest<T>(Failure<T> first, Failure<T> second) =>
+Failure<R> selectFarthest<R>(Failure<R> first, Failure<R> second) =>
     first.position <= second.position ? second : first;
 
 /// Reports the parser failure farthest down in the input string, joining
 /// error messages at the same position.
-Failure<T> selectFarthestJoined<T>(Failure<T> first, Failure<T> second) =>
+Failure<R> selectFarthestJoined<R>(Failure<R> first, Failure<R> second) =>
     first.position > second.position
         ? first
         : first.position < second.position
             ? second
-            : first.failure<T>('${first.message} OR ${second.message}');
+            : first.failure<R>('${first.message} OR ${second.message}');
diff --git a/petitparser/lib/src/reflection/analyzer.dart b/petitparser/lib/src/reflection/analyzer.dart
index 23725d9..de9b65f 100644
--- a/petitparser/lib/src/reflection/analyzer.dart
+++ b/petitparser/lib/src/reflection/analyzer.dart
@@ -104,5 +104,5 @@
 
   /// A unique parser used as a marker in [firstSet] and [followSet]
   /// computations.
-  static final EpsilonParser sentinel = EpsilonParser<void>(null);
+  static final sentinel = EpsilonParser<void>(null);
 }
diff --git a/petitparser/pubspec.yaml b/petitparser/pubspec.yaml
index 38b584c..d20e1b3 100644
--- a/petitparser/pubspec.yaml
+++ b/petitparser/pubspec.yaml
@@ -1,10 +1,9 @@
 name: petitparser
-version: 5.2.0
+version: 5.3.0
 
 homepage: https://petitparser.github.io
 repository: https://github.com/petitparser/dart-petitparser
-description: A dynamic parser framework to build efficient grammars and parsers
-  quickly.
+description: A dynamic parser framework to build efficient grammars and parsers quickly.
 
 environment:
   sdk: '>=2.19.0 <3.0.0'
diff --git a/pubspec_parse/BUILD.gn b/pubspec_parse/BUILD.gn
index 45fd0d6..9fc9acf 100644
--- a/pubspec_parse/BUILD.gn
+++ b/pubspec_parse/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for pubspec_parse-1.2.1
+# This file is generated by package_importer.py for pubspec_parse-1.2.3
 
 import("//build/dart/dart_library.gni")
 
 dart_library("pubspec_parse") {
   package_name = "pubspec_parse"
 
-  language_version = "2.14"
+  language_version = "2.18"
 
   disable_analysis = true
 
diff --git a/pubspec_parse/CHANGELOG.md b/pubspec_parse/CHANGELOG.md
index 069e81c..9ed3837 100644
--- a/pubspec_parse/CHANGELOG.md
+++ b/pubspec_parse/CHANGELOG.md
@@ -1,4 +1,11 @@
-## 1.2.1-dev
+## 1.2.3
+- Added topics to `pubspec.yaml`.
+
+## 1.2.2
+
+- Require Dart SDK >= 2.18.0
+- Required `json_annotation: ^4.8.0`
+- Added support for `topics` field.
 
 ## 1.2.1
 
diff --git a/pubspec_parse/LICENSE b/pubspec_parse/LICENSE
index 9972f6e..4d1ad40 100644
--- a/pubspec_parse/LICENSE
+++ b/pubspec_parse/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2018, the Dart project authors. 
+Copyright 2018, the Dart project authors.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
diff --git a/pubspec_parse/README.md b/pubspec_parse/README.md
index eca3610..916742a 100644
--- a/pubspec_parse/README.md
+++ b/pubspec_parse/README.md
@@ -1,7 +1,12 @@
-[![Build Status](https://github.com/dart-lang/pubspec_parse/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/pubspec_parse/actions?query=workflow%3A"Dart+CI"+branch%3Amaster)
+[![Dart CI](https://github.com/dart-lang/pubspec_parse/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/pubspec_parse/actions/workflows/test-package.yml)
 [![pub package](https://img.shields.io/pub/v/pubspec_parse.svg)](https://pub.dev/packages/pubspec_parse)
+[![package publisher](https://img.shields.io/pub/publisher/pubspec_parse.svg)](https://pub.dev/packages/pubspec_parse/publisher)
+
+## What's this?
 
 Supports parsing `pubspec.yaml` files with robust error reporting and support
 for most of the documented features.
 
+## More information
+
 Read more about the [pubspec format](https://dart.dev/tools/pub/pubspec).
diff --git a/pubspec_parse/analysis_options.yaml b/pubspec_parse/analysis_options.yaml
index fdc60e0..cf5c91f 100644
--- a/pubspec_parse/analysis_options.yaml
+++ b/pubspec_parse/analysis_options.yaml
@@ -35,7 +35,6 @@
     - prefer_const_declarations
     - prefer_expression_function_bodies
     - prefer_final_locals
-    - prefer_interpolation_to_compose_strings
     - prefer_relative_imports
     - require_trailing_commas
     - sort_pub_dependencies
@@ -43,7 +42,6 @@
     - throw_in_finally
     - unnecessary_await_in_return
     - unnecessary_lambdas
-    - unnecessary_null_aware_assignments
     - unnecessary_parenthesis
     - unnecessary_statements
     - use_string_buffers
diff --git a/pubspec_parse/lib/src/dependency.dart b/pubspec_parse/lib/src/dependency.dart
index 8b59f73..e345dfd 100644
--- a/pubspec_parse/lib/src/dependency.dart
+++ b/pubspec_parse/lib/src/dependency.dart
@@ -223,7 +223,7 @@
   @JsonKey(fromJson: parseGitUriOrNull, disallowNullValue: true)
   final Uri? url;
 
-  @JsonKey(ignore: true)
+  @JsonKey(includeFromJson: false, includeToJson: false)
   String? _nameOfPackage;
 
   /// The name of this package on the package repository.
diff --git a/pubspec_parse/lib/src/pubspec.dart b/pubspec_parse/lib/src/pubspec.dart
index 9943da8..188af31 100644
--- a/pubspec_parse/lib/src/pubspec.dart
+++ b/pubspec_parse/lib/src/pubspec.dart
@@ -44,6 +44,9 @@
   /// support or funding.
   final List<Uri>? funding;
 
+  /// Optional field to list the topics that this packages belongs to.
+  final List<String>? topics;
+
   /// Optional field for specifying included screenshot files.
   @JsonKey(fromJson: parseScreenshots)
   final List<Screenshot>? screenshots;
@@ -106,6 +109,7 @@
     this.repository,
     this.issueTracker,
     this.funding,
+    this.topics,
     this.screenshots,
     this.documentation,
     this.description,
diff --git a/pubspec_parse/lib/src/pubspec.g.dart b/pubspec_parse/lib/src/pubspec.g.dart
index a1312a5..6229994 100644
--- a/pubspec_parse/lib/src/pubspec.g.dart
+++ b/pubspec_parse/lib/src/pubspec.g.dart
@@ -32,6 +32,8 @@
               (v) => (v as List<dynamic>?)
                   ?.map((e) => Uri.parse(e as String))
                   .toList()),
+          topics: $checkedConvert('topics',
+              (v) => (v as List<dynamic>?)?.map((e) => e as String).toList()),
           screenshots: $checkedConvert(
               'screenshots', (v) => parseScreenshots(v as List?)),
           documentation: $checkedConvert('documentation', (v) => v as String?),
diff --git a/pubspec_parse/pubspec.yaml b/pubspec_parse/pubspec.yaml
index 23f15c1..a2b189d 100644
--- a/pubspec_parse/pubspec.yaml
+++ b/pubspec_parse/pubspec.yaml
@@ -1,25 +1,27 @@
 name: pubspec_parse
+version: 1.2.3
 description: >-
   Simple package for parsing pubspec.yaml files with a type-safe API and rich
   error reporting.
-version: 1.2.1
 repository: https://github.com/dart-lang/pubspec_parse
+topics:
+- dart-pub
 
 environment:
-  sdk: '>=2.14.0 <3.0.0'
+  sdk: '>=2.18.0 <3.0.0'
 
 dependencies:
   checked_yaml: ^2.0.1
   collection: ^1.15.0
-  json_annotation: ^4.6.0
+  json_annotation: ^4.8.0
   pub_semver: ^2.0.0
   yaml: ^3.0.0
 
 dev_dependencies:
   build_runner: ^2.0.3
   build_verify: '>=2.0.0 <4.0.0'
-  json_serializable: ^6.0.0
-  lints: ^1.0.0
+  json_serializable: ^6.6.0
+  lints: ^2.0.0
   path: ^1.5.1
   # Needed because we are configuring `combining_builder`
   source_gen: ^1.0.0
diff --git a/retry/BUILD.gn b/retry/BUILD.gn
index 399a99f..0bdc444 100644
--- a/retry/BUILD.gn
+++ b/retry/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for retry-3.1.0
+# This file is generated by package_importer.py for retry-3.1.1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/retry/CHANGELOG.md b/retry/CHANGELOG.md
index 8d72551..cb5333e 100644
--- a/retry/CHANGELOG.md
+++ b/retry/CHANGELOG.md
@@ -1,3 +1,6 @@
+## v3.1.1
+ * Add `topics` to pubspec.
+
 ## v3.1.0
  * Stable null-safety release.
 
diff --git a/retry/analysis_options.yaml b/retry/analysis_options.yaml
index 108d105..572dd23 100644
--- a/retry/analysis_options.yaml
+++ b/retry/analysis_options.yaml
@@ -1 +1 @@
-include: package:pedantic/analysis_options.yaml
+include: package:lints/recommended.yaml
diff --git a/retry/mono_pkg.yaml b/retry/mono_pkg.yaml
index 5127d3e..7fd029b 100644
--- a/retry/mono_pkg.yaml
+++ b/retry/mono_pkg.yaml
@@ -1,8 +1,8 @@
-dart:
-  - beta
+sdk:
+  - stable
 stages:
   - analyze:
-      - dartanalyzer
-      - dartfmt
+      - analyze
+      - format
   - tests:
       - test
diff --git a/retry/pubspec.yaml b/retry/pubspec.yaml
index 8152a93..e43e776 100644
--- a/retry/pubspec.yaml
+++ b/retry/pubspec.yaml
@@ -1,5 +1,5 @@
 name: retry
-version: 3.1.0
+version: 3.1.1
 description: |
   Utility for wrapping an asynchronous function in automatic retry logic with
   exponential back-off, useful when making requests over network.
@@ -8,6 +8,9 @@
 issue_tracker: https://github.com/google/dart-neats/labels/pkg:retry
 dev_dependencies:
   test: ^1.16.0-nullsafety.13
-  pedantic: ^1.4.0
+  lints: ^1.0.0
 environment:
-  sdk: ">=2.12.0-0 <3.0.0"
+  sdk: ">=2.12.0 <3.0.0"
+topics:
+ - network
+ - http
diff --git a/source_gen/BUILD.gn b/source_gen/BUILD.gn
index d2b2dfa..c6e184c 100644
--- a/source_gen/BUILD.gn
+++ b/source_gen/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by package_importer.py for source_gen-1.2.7
+# This file is generated by package_importer.py for source_gen-1.3.2
 
 import("//build/dart/dart_library.gni")
 
diff --git a/source_gen/CHANGELOG.md b/source_gen/CHANGELOG.md
index b765fdc..f558e00 100644
--- a/source_gen/CHANGELOG.md
+++ b/source_gen/CHANGELOG.md
@@ -1,3 +1,22 @@
+## 1.3.2
+
+* Make `TypeChecker.isAssignableFromType()` null safe.
+
+## 1.3.1
+
+* Always use a Uri in `part of` directives (previously a name would be used if
+  the library had a non-empty one).
+
+## 1.3.0
+
+* Add support for `build_extensions` configuration of builders producing
+  multiple files. For example:
+  `build_extensions: { '.dart': ['.stub.dart', '.web.dart', '.vm.dart'] }`
+* Avoid throwing when a type without a backing class is checked with
+  `TypeChecker`.
+* Include imports, exports, and part directives in `LibraryReader.allElements`.
+  This allows `GeneratorForAnnotation` to target annotated directives.
+
 ## 1.2.7
 
 * Update the value of the pubspec `repository` field.
diff --git a/source_gen/README.md b/source_gen/README.md
index 60a2b16..4c02b57 100644
--- a/source_gen/README.md
+++ b/source_gen/README.md
@@ -216,3 +216,8 @@
 [Full example package]: https://github.com/dart-lang/source_gen/tree/master/example
 [example usage]: https://github.com/dart-lang/source_gen/tree/master/example_usage
 [outputs]: https://github.com/dart-lang/build/blob/master/docs/writing_a_builder.md#configuring-outputs
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
diff --git a/source_gen/lib/builder.dart b/source_gen/lib/builder.dart
index 80688b3..7a7a3b2 100644
--- a/source_gen/lib/builder.dart
+++ b/source_gen/lib/builder.dart
@@ -120,7 +120,7 @@
 
     final inputLibrary = await buildStep.inputLibrary;
     final outputId = buildStep.allowedOutputs.single;
-    final partOf = nameOfPartial(inputLibrary, buildStep.inputId, outputId);
+    final partOfUri = uriOfPartial(inputLibrary, buildStep.inputId, outputId);
 
     // Ensure that the input has a correct `part` statement.
     final libraryUnit =
@@ -143,7 +143,7 @@
     final output = '''
 $defaultFileHeader
 ${languageOverrideForLibrary(inputLibrary)}$ignoreForFile$preamble
-part of $partOf;
+part of '$partOfUri';
 
 $assets
 ''';
diff --git a/source_gen/lib/src/builder.dart b/source_gen/lib/src/builder.dart
index 6c50541..e380828 100644
--- a/source_gen/lib/src/builder.dart
+++ b/source_gen/lib/src/builder.dart
@@ -124,13 +124,13 @@
 
     if (!_isLibraryBuilder) {
       final asset = buildStep.inputId;
-      final name = nameOfPartial(library, asset, outputId);
+      final partOfUri = uriOfPartial(library, asset, outputId);
       contentBuffer.writeln();
 
       if (this is PartBuilder) {
         contentBuffer
           ..write(languageOverrideForLibrary(library))
-          ..writeln('part of $name;');
+          ..writeln('part of \'$partOfUri\';');
         final part = computePartUrl(buildStep.inputId, outputId);
 
         final libraryUnit =
diff --git a/source_gen/lib/src/library.dart b/source_gen/lib/src/library.dart
index 6cd1d0f..9c59015 100644
--- a/source_gen/lib/src/library.dart
+++ b/source_gen/lib/src/library.dart
@@ -34,7 +34,13 @@
   }
 
   /// All of the declarations in this library.
-  Iterable<Element> get allElements => [element, ...element.topLevelElements];
+  Iterable<Element> get allElements => [
+        element,
+        ...element.topLevelElements,
+        ...element.libraryImports,
+        ...element.libraryExports,
+        ...element.parts,
+      ];
 
   /// All of the declarations in this library annotated with [checker].
   Iterable<AnnotatedElement> annotatedWith(
diff --git a/source_gen/lib/src/type_checker.dart b/source_gen/lib/src/type_checker.dart
index 10a85e1..46db230 100644
--- a/source_gen/lib/src/type_checker.dart
+++ b/source_gen/lib/src/type_checker.dart
@@ -167,14 +167,26 @@
       (element is InterfaceElement && element.allSupertypes.any(isExactlyType));
 
   /// Returns `true` if [staticType] can be assigned to this type.
-  bool isAssignableFromType(DartType staticType) =>
-      isAssignableFrom(staticType.element!);
+  bool isAssignableFromType(DartType staticType) {
+    final element = staticType.element;
+    return element != null && isAssignableFrom(element);
+  }
 
   /// Returns `true` if representing the exact same class as [element].
   bool isExactly(Element element);
 
   /// Returns `true` if representing the exact same type as [staticType].
-  bool isExactlyType(DartType staticType) => isExactly(staticType.element!);
+  ///
+  /// This will always return false for types without a backingclass such as
+  /// `void` or function types.
+  bool isExactlyType(DartType staticType) {
+    final element = staticType.element;
+    if (element != null) {
+      return isExactly(element);
+    } else {
+      return false;
+    }
+  }
 
   /// Returns `true` if representing a super class of [element].
   ///
diff --git a/source_gen/lib/src/utils.dart b/source_gen/lib/src/utils.dart
index c8f2d46..f15fd59 100644
--- a/source_gen/lib/src/utils.dart
+++ b/source_gen/lib/src/utils.dart
@@ -43,29 +43,10 @@
         .whereType<PartDirective>()
         .any((e) => e.uri.stringValue == part);
 
-/// Returns a name suitable for `part of "..."` when pointing to [element].
-String nameOfPartial(LibraryElement element, AssetId source, AssetId output) {
-  if (element.name.isNotEmpty) {
-    return element.name;
-  }
-
+/// Returns a uri suitable for `part of "..."` when pointing to [element].
+String uriOfPartial(LibraryElement element, AssetId source, AssetId output) {
   assert(source.package == output.package);
-  final relativeSourceUri =
-      p.url.relative(source.path, from: p.url.dirname(output.path));
-  return '\'$relativeSourceUri\'';
-}
-
-/// Returns a suggested library identifier based on [source] path and package.
-String suggestLibraryName(AssetId source) {
-  // lib/test.dart --> [lib/test.dart]
-  final parts = source.path.split('/');
-  // [lib/test.dart] --> [lib/test]
-  parts[parts.length - 1] = parts.last.split('.').first;
-  // [lib/test] --> [test]
-  if (parts.first == 'lib') {
-    parts.removeAt(0);
-  }
-  return '${source.package}.${parts.join('.')}';
+  return p.url.relative(source.path, from: p.url.dirname(output.path));
 }
 
 /// Returns what 'part "..."' URL is needed to import [output] from [input].
@@ -176,7 +157,11 @@
   Map<String, List<String>> defaultExtensions,
 ) {
   final extensionsOption = optionsMap?.remove('build_extensions');
-  if (extensionsOption == null) return defaultExtensions;
+  if (extensionsOption == null) {
+    // defaultExtensions are provided by the builder author, not the end user.
+    // It should be safe to skip validation.
+    return defaultExtensions;
+  }
 
   if (extensionsOption is! Map) {
     throw ArgumentError(
@@ -195,15 +180,19 @@
       );
     }
 
-    final output = entry.value;
-    if (output is! String || !output.endsWith('.dart')) {
-      throw ArgumentError(
-        'Invalid output extension `$output`. It should be a '
-        'string ending with `.dart`',
-      );
+    final output = (entry.value is List) ? entry.value as List : [entry.value];
+
+    for (var i = 0; i < output.length; i++) {
+      final o = output[i];
+      if (o is! String || (i == 0 && !o.endsWith('.dart'))) {
+        throw ArgumentError(
+          'Invalid output extension `${entry.value}`. It should be a string '
+          'or a list of strings with the first ending with `.dart`',
+        );
+      }
     }
 
-    result[input] = [output];
+    result[input] = output.cast<String>().toList();
   }
 
   if (result.isEmpty) {
diff --git a/source_gen/pubspec.yaml b/source_gen/pubspec.yaml
index e9ebedb..69e2e39 100644
--- a/source_gen/pubspec.yaml
+++ b/source_gen/pubspec.yaml
@@ -1,5 +1,5 @@
 name: source_gen
-version: 1.2.7
+version: 1.3.2
 description: >-
   Source code generation builders and utilities for the Dart build system
 repository: https://github.com/dart-lang/source_gen/tree/master/source_gen
diff --git a/tuple/BUILD.gn b/tuple/BUILD.gn
index 7fd4d1d..003c85b 100644
--- a/tuple/BUILD.gn
+++ b/tuple/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for tuple-2.0.1
+# This file is generated by package_importer.py for tuple-2.0.2
 
 import("//build/dart/dart_library.gni")
 
 dart_library("tuple") {
   package_name = "tuple"
 
-  language_version = "2.14"
+  language_version = "2.17"
 
   disable_analysis = true
 
diff --git a/tuple/CHANGELOG.md b/tuple/CHANGELOG.md
index ffbc073..389f17e 100644
--- a/tuple/CHANGELOG.md
+++ b/tuple/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 2.0.2
+
+- Update the readme to indicate that this package is considered feature
+  complete.
+- Require Dart 2.17.
+
 ## 2.0.1
 
 - Remove the dependency on `package:quiver`.
diff --git a/tuple/README.md b/tuple/README.md
index 675ccf6..fece9f2 100644
--- a/tuple/README.md
+++ b/tuple/README.md
@@ -4,6 +4,25 @@
 
 A library providing a tuple data structure.
 
+## Status - complete
+
+We consider this package to be feature complete. With Dart 3.0, users now have
+the ability to use [Records](https://dart.dev/language/records):
+
+> Records are an anonymous, immutable, aggregate type. Like other collection
+  types, they let you bundle multiple objects into a single object. 
+
+```dart
+  var record = (123, true);
+  print('${record.$1}: ${record.$2}');
+```
+
+By and large, Records serve the same use cases that `package:tuple` had been
+used for. New users coming to this package should likely look at using Dart
+Records instead. Existing uses of package:tuple will continue to work, however
+we don't intend to enhance the functionality of this package; we will continue
+to maintain this package from the POV of bug fixes.
+
 ## Usage example
 
 ```dart
diff --git a/tuple/analysis_options.yaml b/tuple/analysis_options.yaml
index 500473b..1498fff 100644
--- a/tuple/analysis_options.yaml
+++ b/tuple/analysis_options.yaml
@@ -2,4 +2,5 @@
 
 analyzer:
   language:
+    strict-casts: true
     strict-inference: true
diff --git a/tuple/pubspec.yaml b/tuple/pubspec.yaml
index 0f1353a..3cad7f1 100644
--- a/tuple/pubspec.yaml
+++ b/tuple/pubspec.yaml
@@ -1,13 +1,13 @@
 name: tuple
-version: 2.0.1
+version: 2.0.2
 description: A library providing a tuple data structure.
 repository: https://github.com/google/tuple.dart
 
 environment:
-  sdk: '>=2.14.0 <3.0.0'
+  sdk: '>=2.17.0 <3.0.0'
 
 dependencies:
 
 dev_dependencies:
-  lints: ^1.0.0
+  lints: ^2.0.0
   test: ^1.16.0
diff --git a/vm_service/BUILD.gn b/vm_service/BUILD.gn
index 72eb39a..9c45680 100644
--- a/vm_service/BUILD.gn
+++ b/vm_service/BUILD.gn
@@ -1,11 +1,11 @@
-# This file is generated by package_importer.py for vm_service-11.1.0
+# This file is generated by package_importer.py for vm_service-11.3.0
 
 import("//build/dart/dart_library.gni")
 
 dart_library("vm_service") {
   package_name = "vm_service"
 
-  language_version = "2.15"
+  language_version = "2.19"
 
   disable_analysis = true
 
diff --git a/vm_service/CHANGELOG.md b/vm_service/CHANGELOG.md
index 87b3339..6a93e48 100644
--- a/vm_service/CHANGELOG.md
+++ b/vm_service/CHANGELOG.md
@@ -1,4 +1,16 @@
-# Changelog
+## 11.3.0
+- Update to version `4.4` of the spec.
+- Add `label` property to `InstanceRef`.
+- Add `kUserTag` to `InstanceKind`.
+
+## 11.2.1
+- Prevent `VmServerConnection` from converting `SentinelException`s into
+  `RPCError`s.
+
+## 11.2.0
+- Update to version `4.3` of the spec.
+- Add `isSealed`, `isMixinClass`, `isBaseClass`, `isInterfaceClass`, and
+  `isFinal` properties to `Class`.
 
 ## 11.1.0
 - Reduce number of type checks in `toJson()` methods.
diff --git a/vm_service/analysis_options.yaml b/vm_service/analysis_options.yaml
index 5528616..811696e 100644
--- a/vm_service/analysis_options.yaml
+++ b/vm_service/analysis_options.yaml
@@ -2,8 +2,7 @@
 
 linter:
   rules:
+    # still 6 errors in lib/src/vm_service.dart
+    #- comment_references
     - directives_ordering
-    - prefer_generic_function_type_aliases
-    - prefer_initializing_formals
     - prefer_single_quotes
-    - unnecessary_this
diff --git a/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java b/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java
index 5b31a47..b941a5d 100644
--- a/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java
+++ b/vm_service/java/test/org/dartlang/vm/service/VmServiceTest.java
@@ -328,7 +328,7 @@
   @SuppressWarnings("SameParameterValue")
   private static void vmAddBreakpoint(Isolate isolate, ScriptRef script, int lineNum) {
     final OpLatch latch = new OpLatch();
-    vmService.addBreakpoint(isolate.getId(), script.getId(), lineNum, new BreakpointConsumer() {
+    vmService.addBreakpoint(isolate.getId(), script.getId(), lineNum, new AddBreakpointConsumer() {
       @Override
       public void onError(RPCError error) {
         showRPCError(error);
@@ -340,6 +340,11 @@
         System.out.println("  BreakpointNumber:" + response.getBreakpointNumber());
         latch.opComplete();
       }
+
+      @Override
+      public void received(Sentinel response) {
+        showSentinel(response);
+      }
     });
     latch.waitAndAssertOpComplete();
   }
@@ -395,7 +400,7 @@
     System.out.println("Getting coverage information for " + isolate.getId());
     final long startTime = System.currentTimeMillis();
     final ResultLatch<SourceReport> latch = new ResultLatch<>();
-    vmService.getSourceReport(isolate.getId(), Collections.singletonList(SourceReportKind.Coverage), new SourceReportConsumer() {
+    vmService.getSourceReport(isolate.getId(), Collections.singletonList(SourceReportKind.Coverage), new GetSourceReportConsumer() {
       @Override
       public void onError(RPCError error) {
         showRPCError(error);
@@ -408,6 +413,11 @@
         System.out.println("  Range count: " + response.getRanges().size());
         latch.setValue(response);
       }
+
+      @Override
+      public void received(Sentinel response) {
+        showSentinel(response);
+      }
     });
     return latch.getValue();
   }
@@ -496,7 +506,7 @@
 
   private static void vmGetStack(Isolate isolate) {
     final ResultLatch<Stack> latch = new ResultLatch<>();
-    vmService.getStack(isolate.getId(), new StackConsumer() {
+    vmService.getStack(isolate.getId(), new GetStackConsumer() {
       @Override
       public void onError(RPCError error) {
         showRPCError(error);
@@ -506,6 +516,11 @@
       public void received(Stack stack) {
         latch.setValue(stack);
       }
+
+      @Override
+      public void received(Sentinel response) {
+        showSentinel(response);
+      }
     });
     Stack stack = latch.getValue();
     System.out.println("Received Stack response");
@@ -576,7 +591,7 @@
         System.out.println("  StartTime: " + response.getStartTime());
         for (IsolateRef isolate : response.getIsolates()) {
           System.out.println("  Isolate " + isolate.getNumber() + ", " + isolate.getId() + ", "
-                  + isolate.getName());
+              + isolate.getName());
         }
         latch.setValue(response.getIsolates());
       }
@@ -587,7 +602,7 @@
   private static void vmPauseOnException(IsolateRef isolate, ExceptionPauseMode mode) {
     System.out.println("Request pause on exception: " + mode);
     final OpLatch latch = new OpLatch();
-    vmService.setIsolatePauseMode(isolate.getId(), mode, new SuccessConsumer() {
+    vmService.setIsolatePauseMode(isolate.getId(), mode, /*shouldPauseOnExit=*/true, new SetIsolatePauseModeConsumer() {
         @Override
         public void onError(RPCError error) {
             showRPCError(error);
@@ -598,13 +613,18 @@
             System.out.println("Successfully set pause on exception");
             latch.opComplete();
         }
+
+        @Override
+        public void received(Sentinel response) {
+          showSentinel(response);
+        }
     });
     latch.waitAndAssertOpComplete();
   }
 
   private static void vmResume(IsolateRef isolateRef, final StepOption step) {
     final String id = isolateRef.getId();
-    vmService.resume(id, step, null, new SuccessConsumer() {
+    vmService.resume(id, step, null, new ResumeConsumer() {
       @Override
       public void onError(RPCError error) {
         showRPCError(error);
@@ -618,6 +638,11 @@
           System.out.println("Step " + step + " isolate " + id);
         }
       }
+
+      @Override
+      public void received(Sentinel response) {
+        showSentinel(response);
+      }
     });
     // Do not wait for confirmation, but display error if it occurs
   }
diff --git a/vm_service/java/version.properties b/vm_service/java/version.properties
index 5aa69a7..c350790 100644
--- a/vm_service/java/version.properties
+++ b/vm_service/java/version.properties
@@ -1 +1 @@
-version=4.2
+version=4.4
diff --git a/vm_service/lib/src/dart_io_extensions.dart b/vm_service/lib/src/dart_io_extensions.dart
index 3433a05..4ce6cc1 100644
--- a/vm_service/lib/src/dart_io_extensions.dart
+++ b/vm_service/lib/src/dart_io_extensions.dart
@@ -869,7 +869,7 @@
   final String name;
 }
 
-/// A [File] contains information about reads and writes to a currently opened file.
+/// Contains information about reads and writes to a currently opened file.
 class OpenFile extends Response implements OpenFileRef {
   static OpenFile? parse(Map<String, dynamic>? json) =>
       json == null ? null : OpenFile._fromJson(json);
diff --git a/vm_service/lib/src/snapshot_graph.dart b/vm_service/lib/src/snapshot_graph.dart
index 11d437f..4ed6919 100644
--- a/vm_service/lib/src/snapshot_graph.dart
+++ b/vm_service/lib/src/snapshot_graph.dart
@@ -291,7 +291,7 @@
   /// The list of classes found in this snapshot.
   List<HeapSnapshotClass> get classes => _classes;
 
-  /// At least as big as the sum of all [HeapSnapshotObject.referenceCount].
+  /// At least as big as the sum of all [HeapSnapshotObject.references].
   int get referenceCount => _referenceCount;
 
   /// The list of objects found in this snapshot.
diff --git a/vm_service/lib/src/vm_service.dart b/vm_service/lib/src/vm_service.dart
index 9789fe6..d55af29 100644
--- a/vm_service/lib/src/vm_service.dart
+++ b/vm_service/lib/src/vm_service.dart
@@ -28,7 +28,7 @@
         HeapSnapshotObjectNoData,
         HeapSnapshotObjectNullData;
 
-const String vmServiceVersion = '4.2.0';
+const String vmServiceVersion = '4.4.0';
 
 /// @optional
 const String optional = 'optional';
@@ -283,7 +283,7 @@
   /// breakpoints.
   ///
   /// If no breakpoint is possible at that line, the `102` (Cannot add
-  /// breakpoint) [RPC error] code is returned.
+  /// breakpoint) RPC error code is returned.
   ///
   /// Note that breakpoints are added and removed on a per-isolate basis.
   ///
@@ -320,7 +320,7 @@
   /// breakpoints.
   ///
   /// If no breakpoint is possible at that line, the `102` (Cannot add
-  /// breakpoint) [RPC error] code is returned.
+  /// breakpoint) RPC error code is returned.
   ///
   /// Note that breakpoints are added and removed on a per-isolate basis.
   ///
@@ -342,7 +342,7 @@
   /// entrypoint of some function.
   ///
   /// If no breakpoint is possible at the function entry, the `102` (Cannot add
-  /// breakpoint) [RPC error] code is returned.
+  /// breakpoint) RPC error code is returned.
   ///
   /// If `isolateId` refers to an isolate which has exited, then the `Collected`
   /// [Sentinel] is returned.
@@ -394,7 +394,7 @@
   /// If `isolateId` refers to an isolate which has exited, then the `Collected`
   /// [Sentinel] is returned.
   ///
-  /// If invocation triggers a failed compilation then [RPC error] 113
+  /// If invocation triggers a failed compilation then [RPCError] 113
   /// "Expression compilation error" is returned.
   ///
   /// If a runtime error occurs while evaluating the invocation, an [ErrorRef]
@@ -440,7 +440,7 @@
   /// as a result of this evaluation are ignored. Defaults to false if not
   /// provided.
   ///
-  /// If the expression fails to parse and compile, then [RPC error] 113
+  /// If the expression fails to parse and compile, then [RPCError] 113
   /// "Expression compilation error" is returned.
   ///
   /// If an error occurs while evaluating the expression, an [ErrorRef]
@@ -475,7 +475,7 @@
   /// as a result of this evaluation are ignored. Defaults to false if not
   /// provided.
   ///
-  /// If the expression fails to parse and compile, then [RPC error] 113
+  /// If the expression fails to parse and compile, then [RPCError] 113
   /// "Expression compilation error" is returned.
   ///
   /// If an error occurs while evaluating the expression, an [ErrorRef]
@@ -519,9 +519,9 @@
 
   /// The `getAllocationTraces` RPC allows for the retrieval of allocation
   /// traces for objects of a specific set of types (see
-  /// [setTraceClassAllocation]). Only samples collected in the time range
-  /// `[timeOriginMicros, timeOriginMicros + timeExtentMicros]` will be
-  /// reported.
+  /// [VmServiceInterface.setTraceClassAllocation]). Only samples collected in
+  /// the time range `[timeOriginMicros, timeOriginMicros + timeExtentMicros]`
+  /// will be reported.
   ///
   /// If `classId` is provided, only traces for allocations with the matching
   /// `classId` will be reported.
@@ -555,7 +555,7 @@
   /// profiler. Only samples collected in the time range `[timeOriginMicros,
   /// timeOriginMicros + timeExtentMicros]` will be reported.
   ///
-  /// If the profiler is disabled, an [RPC error] response will be returned.
+  /// If the profiler is disabled, an [RPCError] response will be returned.
   ///
   /// If `isolateId` refers to an isolate which has exited, then the `Collected`
   /// [Sentinel] is returned.
@@ -615,7 +615,7 @@
   /// yet been garbage collected.
   ///
   /// `objectId` is the ID of the `Class` to retrieve instances for. `objectId`
-  /// must be the ID of a `Class`, otherwise an [RPC error] is returned.
+  /// must be the ID of a `Class`, otherwise an [RPCError] is returned.
   ///
   /// `limit` is the maximum number of instances to be returned.
   ///
@@ -643,8 +643,9 @@
 
   /// The `getInstancesAsList` RPC is used to retrieve a set of instances which
   /// are of a specific class. This RPC returns an `InstanceRef` corresponding
-  /// to a Dart `List` that contains the requested instances. This is what
-  /// distinguishes this RPC from `getInstances`, which returns an
+  /// to a Dart `List<dynamic>` that contains the requested instances. This
+  /// `List` is not growable, but it is otherwise mutable. The response type is
+  /// what distinguishes this RPC from `getInstances`, which returns an
   /// `InstanceSet`.
   ///
   /// The order of the instances is undefined (i.e., not related to allocation
@@ -656,7 +657,7 @@
   /// yet been garbage collected.
   ///
   /// `objectId` is the ID of the `Class` to retrieve instances for. `objectId`
-  /// must be the ID of a `Class`, otherwise an [RPC error] is returned.
+  /// must be the ID of a `Class`, otherwise an [RPCError] is returned.
   ///
   /// If `includeSubclasses` is true, instances of subclasses of the specified
   /// class will be included in the set.
@@ -921,8 +922,8 @@
   /// The `timeOriginMicros` parameter is the beginning of the time range used
   /// to filter timeline events. It uses the same monotonic clock as
   /// dart:developer's `Timeline.now` and the VM embedding API's
-  /// `Dart_TimelineGetMicros`. See [getVMTimelineMicros] for access to this
-  /// clock through the service protocol.
+  /// `Dart_TimelineGetMicros`. See [VmServiceInterface.getVMTimelineMicros] for
+  /// access to this clock through the service protocol.
   ///
   /// The `timeExtentMicros` parameter specifies how large the time range used
   /// to filter timeline events should be.
@@ -932,18 +933,18 @@
   /// `(timeOriginMicros, timeOriginMicros + timeExtentMicros)`.
   ///
   /// If `getVMTimeline` is invoked while the current recorder is Callback, an
-  /// [RPC error] with error code `114`, `invalid timeline request`, will be
+  /// [RPCError] with error code `114`, `invalid timeline request`, will be
   /// returned as timeline events are handled by the embedder in this mode.
   ///
   /// If `getVMTimeline` is invoked while the current recorder is one of Fuchsia
-  /// or Macos or Systrace, an [RPC error] with error code `114`, `invalid
+  /// or Macos or Systrace, an [RPCError] with error code `114`, `invalid
   /// timeline request`, will be returned as timeline events are handled by the
   /// OS in these modes.
   ///
-  /// If `getVMTimeline` is invoked while the current recorder is File, an [RPC
-  /// error] with error code `114`, `invalid timeline request`, will be returned
-  /// as timeline events are written directly to a file, and thus cannot be
-  /// retrieved through the VM Service, in this mode.
+  /// If `getVMTimeline` is invoked while the current recorder is File, an
+  /// [RPCError] with error code `114`, `invalid timeline request`, will be
+  /// returned as timeline events are written directly to a file, and thus
+  /// cannot be retrieved through the VM Service, in this mode.
   Future<Timeline> getVMTimeline(
       {int? timeOriginMicros, int? timeExtentMicros});
 
@@ -951,7 +952,7 @@
   /// timeline configuration.
   ///
   /// To change which timeline streams are currently enabled, see
-  /// [setVMTimelineFlags].
+  /// [VmServiceInterface.setVMTimelineFlags].
   ///
   /// See [TimelineFlags].
   Future<TimelineFlags> getVMTimelineFlags();
@@ -960,7 +961,7 @@
   /// clock used by the timeline, similar to `Timeline.now` in `dart:developer`
   /// and `Dart_TimelineGetMicros` in the VM embedding API.
   ///
-  /// See [Timestamp] and [getVMTimeline].
+  /// See [Timestamp] and [VmServiceInterface.getVMTimeline].
   Future<Timestamp> getVMTimelineMicros();
 
   /// The `pause` RPC is used to interrupt a running isolate. The RPC enqueues
@@ -1259,7 +1260,7 @@
   /// stream as a result of invoking this RPC.
   ///
   /// To get the list of currently enabled timeline streams, see
-  /// [getVMTimelineFlags].
+  /// [VmServiceInterface.getVMTimelineFlags].
   ///
   /// See [Success].
   Future<Success> setVMTimelineFlags(List<String> recordedStreams);
@@ -1267,7 +1268,7 @@
   /// The `streamCancel` RPC cancels a stream subscription in the VM.
   ///
   /// If the client is not subscribed to the stream, the `104` (Stream not
-  /// subscribed) [RPC error] code is returned.
+  /// subscribed) RPC error code is returned.
   ///
   /// See [Success].
   Future<Success> streamCancel(String streamId);
@@ -1285,7 +1286,7 @@
   /// the client will begin receiving events from the stream.
   ///
   /// If the client is already subscribed to the stream, the `103` (Stream
-  /// already subscribed) [RPC error] code is returned.
+  /// already subscribed) RPC error code is returned.
   ///
   /// The `streamId` parameter may have the following published values:
   ///
@@ -1790,6 +1791,12 @@
         'id': id,
         'result': response.toJson(),
       });
+    } on SentinelException catch (e) {
+      _responseSink.add({
+        'jsonrpc': '2.0',
+        'id': request['id'],
+        'result': e.sentinel.toJson(),
+      });
     } catch (e, st) {
       final error = e is RPCError
           ? e.toMap()
@@ -2861,6 +2868,9 @@
 
   /// An instance of the Dart class ReceivePort.
   static const String kReceivePort = 'ReceivePort';
+
+  /// An instance of the Dart class UserTag.
+  static const String kUserTag = 'UserTag';
 }
 
 /// A `SentinelKind` is used to distinguish different kinds of `Sentinel`
@@ -3313,6 +3323,21 @@
   /// Is this a const class?
   bool? isConst;
 
+  /// Is this a sealed class?
+  bool? isSealed;
+
+  /// Is this a mixin class?
+  bool? isMixinClass;
+
+  /// Is this a base class?
+  bool? isBaseClass;
+
+  /// Is this an interface class?
+  bool? isInterfaceClass;
+
+  /// Is this a final class?
+  bool? isFinal;
+
   /// Are allocations of this class being traced?
   bool? traceAllocations;
 
@@ -3352,6 +3377,11 @@
     this.library,
     this.isAbstract,
     this.isConst,
+    this.isSealed,
+    this.isMixinClass,
+    this.isBaseClass,
+    this.isInterfaceClass,
+    this.isFinal,
     this.traceAllocations,
     this.interfaces,
     this.fields,
@@ -3382,6 +3412,11 @@
     error = createServiceObject(json['error'], const ['ErrorRef']) as ErrorRef?;
     isAbstract = json['abstract'] ?? false;
     isConst = json['const'] ?? false;
+    isSealed = json['isSealed'] ?? false;
+    isMixinClass = json['isMixinClass'] ?? false;
+    isBaseClass = json['isBaseClass'] ?? false;
+    isInterfaceClass = json['isInterfaceClass'] ?? false;
+    isFinal = json['isFinal'] ?? false;
     traceAllocations = json['traceAllocations'] ?? false;
     superClass =
         createServiceObject(json['super'], const ['ClassRef']) as ClassRef?;
@@ -3415,6 +3450,11 @@
       'library': library?.toJson(),
       'abstract': isAbstract ?? false,
       'const': isConst ?? false,
+      'isSealed': isSealed ?? false,
+      'isMixinClass': isMixinClass ?? false,
+      'isBaseClass': isBaseClass ?? false,
+      'isInterfaceClass': isInterfaceClass ?? false,
+      'isFinal': isFinal ?? false,
       'traceAllocations': traceAllocations ?? false,
       'interfaces': interfaces?.map((f) => f.toJson()).toList(),
       'fields': fields?.map((f) => f.toJson()).toList(),
@@ -3765,7 +3805,7 @@
   String toString() => '[ContextElement value: $value]';
 }
 
-/// See [getCpuSamples] and [CpuSample].
+/// See [VmServiceInterface.getCpuSamples] and [CpuSample].
 class CpuSamples extends Response {
   static CpuSamples? parse(Map<String, dynamic>? json) =>
       json == null ? null : CpuSamples._fromJson(json);
@@ -3930,7 +3970,7 @@
       'sampleCount: $sampleCount, timeOriginMicros: $timeOriginMicros, timeExtentMicros: $timeExtentMicros, pid: $pid, functions: $functions, samples: $samples]';
 }
 
-/// See [getCpuSamples] and [CpuSamples].
+/// See [VmServiceInterface.getCpuSamples] and [CpuSamples].
 class CpuSample {
   static CpuSample? parse(Map<String, dynamic>? json) =>
       json == null ? null : CpuSample._fromJson(json);
@@ -4069,7 +4109,7 @@
 }
 
 /// An `Error` represents a Dart language level error. This is distinct from an
-/// [RPC error].
+/// [RPCError].
 class Error extends Obj implements ErrorRef {
   static Error? parse(Map<String, dynamic>? json) =>
       json == null ? null : Error._fromJson(json);
@@ -5190,6 +5230,13 @@
   @optional
   String? debugName;
 
+  /// The label associated with a UserTag.
+  ///
+  /// Provided for instance kinds:
+  ///  - UserTag
+  @optional
+  String? label;
+
   InstanceRef({
     this.kind,
     this.identityHashCode,
@@ -5210,6 +5257,7 @@
     this.portId,
     this.allocationLocation,
     this.debugName,
+    this.label,
   }) : super(
           id: id,
         );
@@ -5253,6 +5301,7 @@
         createServiceObject(json['allocationLocation'], const ['InstanceRef'])
             as InstanceRef?;
     debugName = json['debugName'];
+    label = json['label'];
   }
 
   @override
@@ -5284,6 +5333,7 @@
     _setIfNotNull(json, 'portId', portId);
     _setIfNotNull(json, 'allocationLocation', allocationLocation?.toJson());
     _setIfNotNull(json, 'debugName', debugName);
+    _setIfNotNull(json, 'label', label);
     return json;
   }
 
@@ -5638,6 +5688,14 @@
   @override
   String? debugName;
 
+  /// The label associated with a UserTag.
+  ///
+  /// Provided for instance kinds:
+  ///  - UserTag
+  @optional
+  @override
+  String? label;
+
   Instance({
     this.kind,
     this.identityHashCode,
@@ -5674,6 +5732,7 @@
     this.portId,
     this.allocationLocation,
     this.debugName,
+    this.label,
   }) : super(
           id: id,
           classRef: classRef,
@@ -5751,6 +5810,7 @@
         createServiceObject(json['allocationLocation'], const ['InstanceRef'])
             as InstanceRef?;
     debugName = json['debugName'];
+    label = json['label'];
   }
 
   @override
@@ -5799,6 +5859,7 @@
     _setIfNotNull(json, 'portId', portId);
     _setIfNotNull(json, 'allocationLocation', allocationLocation?.toJson());
     _setIfNotNull(json, 'debugName', debugName);
+    _setIfNotNull(json, 'label', label);
     return json;
   }
 
@@ -6213,7 +6274,7 @@
       'isolates: $isolates]';
 }
 
-/// See [getInboundReferences].
+/// See [VmServiceInterface.getInboundReferences].
 class InboundReferences extends Response {
   static InboundReferences? parse(Map<String, dynamic>? json) =>
       json == null ? null : InboundReferences._fromJson(json);
@@ -6250,7 +6311,7 @@
   String toString() => '[InboundReferences references: $references]';
 }
 
-/// See [getInboundReferences].
+/// See [VmServiceInterface.getInboundReferences].
 class InboundReference {
   static InboundReference? parse(Map<String, dynamic>? json) =>
       json == null ? null : InboundReference._fromJson(json);
@@ -6309,7 +6370,7 @@
   String toString() => '[InboundReference source: $source]';
 }
 
-/// See [getInstances].
+/// See [VmServiceInterface.getInstances].
 class InstanceSet extends Response {
   static InstanceSet? parse(Map<String, dynamic>? json) =>
       json == null ? null : InstanceSet._fromJson(json);
@@ -6401,7 +6462,7 @@
 
 /// A `Library` provides information about a Dart language library.
 ///
-/// See [setLibraryDebuggable].
+/// See [VmServiceInterface.setLibraryDebuggable].
 class Library extends Obj implements LibraryRef {
   static Library? parse(Map<String, dynamic>? json) =>
       json == null ? null : Library._fromJson(json);
@@ -7130,7 +7191,7 @@
 
 /// A `PortList` contains a list of ports associated with some isolate.
 ///
-/// See [getPort].
+/// See [VmServiceInterface.getPorts].
 class PortList extends Response {
   static PortList? parse(Map<String, dynamic>? json) =>
       json == null ? null : PortList._fromJson(json);
@@ -7226,7 +7287,7 @@
 /// A `ProtocolList` contains a list of all protocols supported by the service
 /// instance.
 ///
-/// See [Protocol] and [getSupportedProtocols].
+/// See [Protocol] and [VmServiceInterface.getSupportedProtocols].
 class ProtocolList extends Response {
   static ProtocolList? parse(Map<String, dynamic>? json) =>
       json == null ? null : ProtocolList._fromJson(json);
@@ -7261,7 +7322,7 @@
   String toString() => '[ProtocolList protocols: $protocols]';
 }
 
-/// See [getSupportedProtocols].
+/// See [VmServiceInterface.getSupportedProtocols].
 class Protocol {
   static Protocol? parse(Map<String, dynamic>? json) =>
       json == null ? null : Protocol._fromJson(json);
@@ -7302,7 +7363,7 @@
       '[Protocol protocolName: $protocolName, major: $major, minor: $minor]';
 }
 
-/// Set [getProcessMemoryUsage].
+/// See [VmServiceInterface.getProcessMemoryUsage].
 class ProcessMemoryUsage extends Response {
   static ProcessMemoryUsage? parse(Map<String, dynamic>? json) =>
       json == null ? null : ProcessMemoryUsage._fromJson(json);
@@ -7477,7 +7538,7 @@
   String toString() => '[RetainingObject value: $value]';
 }
 
-/// See [getRetainingPath].
+/// See [VmServiceInterface.getRetainingPath].
 class RetainingPath extends Response {
   static RetainingPath? parse(Map<String, dynamic>? json) =>
       json == null ? null : RetainingPath._fromJson(json);
@@ -8063,7 +8124,7 @@
 /// The `Stack` class represents the various components of a Dart stack trace
 /// for a given isolate.
 ///
-/// See [getStack].
+/// See [VmServiceInterface.getStack].
 class Stack extends Response {
   static Stack? parse(Map<String, dynamic>? json) =>
       json == null ? null : Stack._fromJson(json);
diff --git a/vm_service/pubspec.yaml b/vm_service/pubspec.yaml
index 4118670..cd19900 100644
--- a/vm_service/pubspec.yaml
+++ b/vm_service/pubspec.yaml
@@ -1,5 +1,5 @@
 name: vm_service
-version: 11.1.0
+version: 11.3.0
 description: >-
   A library to communicate with a service implementing the Dart VM
   service protocol.
@@ -7,7 +7,7 @@
 repository: https://github.com/dart-lang/sdk/tree/main/pkg/vm_service
 
 environment:
-  sdk: '>=2.15.0 <4.0.0'
+  sdk: '>=2.19.0 <4.0.0'
 
 dependencies:
 
@@ -17,7 +17,7 @@
 # See also https://dart.dev/tools/pub/dependencies.
 dev_dependencies:
   async: any
-  # expect: any
+    #  expect: any
   lints: any
   markdown: any
   mockito: any
diff --git a/vm_service/tool/dart/generate_dart.dart b/vm_service/tool/dart/generate_dart.dart
index 9e71674..c96eac0 100644
--- a/vm_service/tool/dart/generate_dart.dart
+++ b/vm_service/tool/dart/generate_dart.dart
@@ -805,6 +805,12 @@
 
     // Close the try block, handle errors
     gen.write(r'''
+      } on SentinelException catch (e) {
+        _responseSink.add({
+          'jsonrpc': '2.0',
+          'id': request['id'],
+          'result': e.sentinel.toJson(),
+        });
       } catch (e, st) {
         final error = e is RPCError
             ? e.toMap()
@@ -1342,7 +1348,7 @@
   @override
   void generate(DartGenerator gen) {
     gen.writeln();
-    if (docs != null) gen.writeDocs(docs);
+    if (docs != null) gen.writeDocs(docs!);
     gen.write('class $name ');
     Type? superType;
     if (superName != null) {
@@ -1876,7 +1882,7 @@
     Type? refType = parent.parent.getType('${parent.name}Ref');
     var interfaceOverride = refType?.hasField(name) ?? false;
 
-    if (docs!.isNotEmpty) gen.writeDocs(docs);
+    if (docs!.isNotEmpty) gen.writeDocs(docs!);
     if (optional) gen.write('@optional ');
     if (overrides || interfaceOverride) gen.write('@override ');
     // Special case where Instance extends Obj, but 'classRef' is not optional
@@ -1946,7 +1952,7 @@
   @override
   void generate(DartGenerator gen) {
     gen.writeln();
-    if (docs != null) gen.writeDocs(docs);
+    if (docs != null) gen.writeDocs(docs!);
     gen.writeStatement('class $name {');
     gen.writeStatement('$name._();');
     gen.writeln();
@@ -1984,7 +1990,7 @@
 
   @override
   void generate(DartGenerator gen) {
-    if (docs != null) gen.writeDocs(docs);
+    if (docs != null) gen.writeDocs(docs!);
     gen.writeStatement("static const String k$name = '$name';");
   }
 }
diff --git a/vm_service/tool/dart/src_gen_dart.dart b/vm_service/tool/dart/src_gen_dart.dart
index cd12a91..9f82415 100644
--- a/vm_service/tool/dart/src_gen_dart.dart
+++ b/vm_service/tool/dart/src_gen_dart.dart
@@ -23,10 +23,14 @@
   DartGenerator({this.colBoundary = defaultColumnBoundary});
 
   /// Write out the given dartdoc text, wrapping lines as necessary to flow
-  /// along the column boundary. If [preferSingle] is true, and the docs would
-  /// fit on a single line, use `///` dartdoc style.
-  void writeDocs(String? docs) {
-    if (docs == null) return;
+  /// along the column boundary.
+  void writeDocs(String docs) {
+    docs = docs
+        .replaceAll('[RPC error] code', 'RPC error code')
+        .replaceAll('[RPC error]', '[RPCError]')
+        .replaceAllMapped(RegExp(r'\[([gs]et[a-zA-Z]+)\]'), (match) {
+      return '[VmServiceInterface.${match[1]}]';
+    });
 
     docs = wrap(docs.trim(), colBoundary - _indent.length - 4);
     // docs = docs.replaceAll('*/', '/');
diff --git a/vm_service/tool/java/generate_java.dart b/vm_service/tool/java/generate_java.dart
index fbd446b..c212b7e 100644
--- a/vm_service/tool/java/generate_java.dart
+++ b/vm_service/tool/java/generate_java.dart
@@ -53,7 +53,7 @@
 late Api api;
 
 /// Convert documentation references
-/// from spec style of [className] to javadoc style {@link className}
+/// from spec style of `className` to javadoc style {@link className}
 String? convertDocLinks(String? doc) {
   if (doc == null) return null;
   var sb = StringBuffer();
@@ -459,6 +459,12 @@
 
   bool get isMultipleReturns => types.length > 1;
 
+  List<TypeRef> get subsetOfTypesThatAreSimple =>
+      types.where((TypeRef typeRef) => typeRef.isSimple).toList();
+
+  List<TypeRef> get subsetOfTypesThatAreNotSimple =>
+      types.where((TypeRef typeRef) => !typeRef.isSimple).toList();
+
   bool get isSimple => types.length == 1 && types.first.isSimple;
 
   bool get isValueAndSentinel => types.length == 2 && hasSentinel;
@@ -939,16 +945,40 @@
   void generateAccessor(TypeWriter writer) {
     if (type.isMultipleReturns && !type.isValueAndSentinel) {
       writer.addMethod(accessorName, [], (StatementWriter w) {
-        w.addImport('com.google.gson.JsonObject');
-        w.addLine('final JsonObject elem = (JsonObject)json.get("$name");');
+        w.addImport('com.google.gson.JsonElement');
+        w.addLine('final JsonElement elem = json.get("$name");');
         w.addLine('if (elem == null) return null;\n');
-        for (TypeRef t in type.types) {
-          String refName = t.name!;
-          if (refName.endsWith('Ref')) {
-            refName = '@${refName.substring(0, refName.length - 3)}';
+        final subsetOfTypesThatAreSimple = type.subsetOfTypesThatAreSimple;
+        final subsetOfTypesThatAreNotSimple =
+            type.subsetOfTypesThatAreNotSimple;
+        if (subsetOfTypesThatAreSimple.isNotEmpty) {
+          w.addImport('com.google.gson.JsonPrimitive');
+          w.addLine('if (elem.isJsonPrimitive()) {');
+          w.addLine('final JsonPrimitive p = (JsonPrimitive) elem;');
+          for (TypeRef t in subsetOfTypesThatAreSimple) {
+            if (t.name == 'boolean') {
+              w.addLine('if (p.isBoolean()) return p.getAsBoolean();');
+            } else if (t.name == 'int') {
+              w.addLine('if (p.isNumber()) return p.getAsInt();');
+            } else if (t.name == 'String') {
+              w.addLine('if (p.isString()) return p.getAsString();');
+            }
           }
-          w.addLine('if (elem.get("type").getAsString().equals("$refName")) '
-              'return new ${t.name}(elem);');
+          w.addLine('}');
+        }
+        if (subsetOfTypesThatAreNotSimple.isNotEmpty) {
+          w.addImport('com.google.gson.JsonObject');
+          w.addLine('if (elem.isJsonObject()) {');
+          w.addLine('final JsonObject o = (JsonObject) elem;');
+          for (TypeRef t in subsetOfTypesThatAreNotSimple) {
+            String refName = t.name!;
+            if (refName.endsWith('Ref')) {
+              refName = '@${refName.substring(0, refName.length - 3)}';
+            }
+            w.addLine('if (o.get("type").getAsString().equals("$refName")) '
+                'return new ${t.name}(o);');
+          }
+          w.addLine('}');
         }
         w.addLine('return null;');
       }, javadoc: docs, returnType: 'Object');