[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