Move isAlwaysExhaustive() to TypeSystemImpl and finish implementation.

Add unit tests.

Change-Id: Iaa201b3083460133b0a24778e044122e8df3c13a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/281867
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
index 94deb2d..a4e9fa7 100644
--- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -856,7 +856,7 @@
           [],
         );
       } else if (error is NonExhaustiveError &&
-          isAlwaysExhaustiveType(expressionType) &&
+          _typeSystem.isAlwaysExhaustive(expressionType) &&
           !hasDefault) {
         _errorReporter.reportErrorForNode(
           CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH,
diff --git a/pkg/analyzer/lib/src/dart/element/type_system.dart b/pkg/analyzer/lib/src/dart/element/type_system.dart
index 0fd788e..36ac716 100644
--- a/pkg/analyzer/lib/src/dart/element/type_system.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_system.dart
@@ -13,6 +13,7 @@
 import 'package:analyzer/dart/element/type_system.dart';
 import 'package:analyzer/error/listener.dart' show ErrorReporter;
 import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/extensions.dart';
 import 'package:analyzer/src/dart/element/generic_inferrer.dart';
 import 'package:analyzer/src/dart/element/greatest_lower_bound.dart';
 import 'package:analyzer/src/dart/element/least_greatest_closure.dart';
@@ -663,6 +664,52 @@
     return orderedArguments;
   }
 
+  /// https://github.com/dart-lang/language
+  /// accepted/future-releases/0546-patterns/feature-specification.md#exhaustiveness-and-reachability
+  bool isAlwaysExhaustive(DartType type) {
+    if (type is InterfaceType) {
+      if (type.isDartCoreBool) {
+        return true;
+      }
+      if (type.isDartCoreNull) {
+        return true;
+      }
+      final element = type.element;
+      if (element is EnumElement) {
+        return true;
+      }
+      if (element is ClassElement && element.isSealed) {
+        return true;
+      }
+      if (element is MixinElement && element.isSealed) {
+        return true;
+      }
+      if (type.isDartAsyncFutureOr) {
+        return isAlwaysExhaustive(type.typeArguments[0]);
+      }
+      return false;
+    } else if (type is TypeParameterTypeImpl) {
+      final promotedBound = type.promotedBound;
+      if (promotedBound != null && isAlwaysExhaustive(promotedBound)) {
+        return true;
+      }
+      final bound = type.element.bound;
+      if (bound != null && isAlwaysExhaustive(bound)) {
+        return true;
+      }
+      return false;
+    } else if (type is RecordType) {
+      for (final field in type.fields) {
+        if (!isAlwaysExhaustive(field.type)) {
+          return false;
+        }
+      }
+      return true;
+    } else {
+      return false;
+    }
+  }
+
   @override
   bool isAssignableTo(DartType fromType, DartType toType) {
     // An actual subtype
diff --git a/pkg/analyzer/lib/src/generated/exhaustiveness.dart b/pkg/analyzer/lib/src/generated/exhaustiveness.dart
index cd0254e..1313b9f 100644
--- a/pkg/analyzer/lib/src/generated/exhaustiveness.dart
+++ b/pkg/analyzer/lib/src/generated/exhaustiveness.dart
@@ -11,7 +11,6 @@
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
 import 'package:analyzer/src/dart/element/element.dart';
-import 'package:analyzer/src/dart/element/extensions.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
 import 'package:analyzer/src/generated/constant.dart';
 
@@ -69,27 +68,6 @@
   return convertConstantValueToSpace(cache, value);
 }
 
-bool isAlwaysExhaustiveType(DartType type) {
-  if (type is InterfaceType) {
-    if (type.isDartCoreBool) return true;
-    if (type.isDartCoreNull) return true;
-    var element = type.element;
-    if (element is EnumElement) return true;
-    if (element is ClassElementImpl && element.isSealed) return true;
-    if (type.isDartAsyncFutureOr) {
-      return isAlwaysExhaustiveType(type.typeArguments[0]);
-    }
-    return false;
-  } else if (type is RecordType) {
-    for (var field in type.fields) {
-      if (!isAlwaysExhaustiveType(field.type)) return false;
-    }
-    return true;
-  } else {
-    return false;
-  }
-}
-
 class AnalyzerEnumOperations
     implements EnumOperations<DartType, EnumElement, FieldElement, DartObject> {
   const AnalyzerEnumOperations();
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index d992aa6..1299b84 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -80,8 +80,6 @@
 import 'package:analyzer/src/generated/element_resolver.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/error_detection_helpers.dart';
-import 'package:analyzer/src/generated/exhaustiveness.dart' as exhaustiveness
-    show isAlwaysExhaustiveType;
 import 'package:analyzer/src/generated/migratable_ast_info_provider.dart';
 import 'package:analyzer/src/generated/migration.dart';
 import 'package:analyzer/src/generated/source.dart';
@@ -1181,7 +1179,7 @@
 
   @override
   bool isAlwaysExhaustiveType(DartType type) {
-    return exhaustiveness.isAlwaysExhaustiveType(type);
+    return typeSystem.isAlwaysExhaustive(type);
   }
 
   @override
diff --git a/pkg/analyzer/test/generated/elements_types_mixin.dart b/pkg/analyzer/test/generated/elements_types_mixin.dart
index 5a28d39..dab9c5b 100644
--- a/pkg/analyzer/test/generated/elements_types_mixin.dart
+++ b/pkg/analyzer/test/generated/elements_types_mixin.dart
@@ -161,6 +161,7 @@
   ClassElementImpl class_({
     required String name,
     bool isAbstract = false,
+    bool isSealed = false,
     InterfaceType? superType,
     List<TypeParameterElement> typeParameters = const [],
     List<InterfaceType> interfaces = const [],
@@ -168,6 +169,8 @@
     List<MethodElementImpl> methods = const [],
   }) {
     var element = ClassElementImpl(name, 0);
+    element.isAbstract = isAbstract;
+    element.isSealed = isSealed;
     element.enclosingElement = testLibrary.definingCompilationUnit;
     element.typeParameters = typeParameters;
     element.supertype = superType ?? typeProvider.objectType;
@@ -204,6 +207,22 @@
     );
   }
 
+  EnumElementImpl enum_({
+    required String name,
+    required List<ConstFieldElementImpl> constants,
+  }) {
+    var element = EnumElementImpl(name, 0);
+    element.enclosingElement = testLibrary.definingCompilationUnit;
+    element.fields = constants;
+    return element;
+  }
+
+  ConstFieldElementImpl enumConstant_(
+    String name,
+  ) {
+    return ConstFieldElementImpl(name, 0)..isEnumConstant = true;
+  }
+
   FunctionTypeImpl functionType({
     required List<TypeParameterElement> typeFormals,
     required List<ParameterElement> parameters,
@@ -305,7 +324,7 @@
   }
 
   InterfaceType interfaceType(
-    ClassElement element, {
+    InterfaceElement element, {
     List<DartType> typeArguments = const [],
     required NullabilitySuffix nullabilitySuffix,
   }) {
@@ -454,11 +473,13 @@
 
   MixinElementImpl mixin_({
     required String name,
+    bool isSealed = false,
     List<TypeParameterElement> typeParameters = const [],
     List<InterfaceType>? constraints,
     List<InterfaceType> interfaces = const [],
   }) {
     var element = MixinElementImpl(name, 0);
+    element.isSealed = isSealed;
     element.enclosingElement = testLibrary.definingCompilationUnit;
     element.typeParameters = typeParameters;
     element.superclassConstraints = constraints ?? [typeProvider.objectType];
diff --git a/pkg/analyzer/test/src/dart/element/always_exhaustive_test.dart b/pkg/analyzer/test/src/dart/element/always_exhaustive_test.dart
new file mode 100644
index 0000000..18e1d4d
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/element/always_exhaustive_test.dart
@@ -0,0 +1,143 @@
+// 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/element/type.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../../generated/type_system_base.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(IsAlwaysExhaustiveTest);
+  });
+}
+
+@reflectiveTest
+class IsAlwaysExhaustiveTest extends AbstractTypeSystemTest {
+  void isAlwaysExhaustive(DartType type) {
+    expect(typeSystem.isAlwaysExhaustive(type), isTrue);
+  }
+
+  void isNotAlwaysExhaustive(DartType type) {
+    expect(typeSystem.isAlwaysExhaustive(type), isFalse);
+  }
+
+  test_class_bool() {
+    isAlwaysExhaustive(boolNone);
+    isAlwaysExhaustive(boolQuestion);
+  }
+
+  test_class_int() {
+    isNotAlwaysExhaustive(intNone);
+    isNotAlwaysExhaustive(intQuestion);
+  }
+
+  test_class_Null() {
+    isAlwaysExhaustive(nullNone);
+    isAlwaysExhaustive(nullQuestion);
+  }
+
+  test_class_sealed() {
+    final A = class_(name: 'A', isSealed: true);
+    isAlwaysExhaustive(interfaceTypeNone(A));
+    isAlwaysExhaustive(interfaceTypeQuestion(A));
+  }
+
+  test_enum() {
+    final E = enum_(name: 'E', constants: []);
+    isAlwaysExhaustive(interfaceTypeNone(E));
+    isAlwaysExhaustive(interfaceTypeQuestion(E));
+  }
+
+  test_futureOr() {
+    isAlwaysExhaustive(futureOrNone(boolNone));
+    isAlwaysExhaustive(futureOrQuestion(boolNone));
+
+    isAlwaysExhaustive(futureOrNone(boolQuestion));
+    isAlwaysExhaustive(futureOrQuestion(boolQuestion));
+
+    isNotAlwaysExhaustive(futureOrNone(intNone));
+    isNotAlwaysExhaustive(futureOrQuestion(intNone));
+  }
+
+  test_mixin_sealed() {
+    final M = mixin_(name: 'M', isSealed: true);
+    isAlwaysExhaustive(interfaceTypeNone(M));
+    isAlwaysExhaustive(interfaceTypeQuestion(M));
+  }
+
+  test_recordType() {
+    isAlwaysExhaustive(
+      recordTypeNone(
+        positionalTypes: [boolNone],
+      ),
+    );
+
+    isAlwaysExhaustive(
+      recordTypeNone(
+        namedTypes: {
+          'f0': boolNone,
+        },
+      ),
+    );
+
+    isNotAlwaysExhaustive(
+      recordTypeNone(
+        positionalTypes: [intNone],
+      ),
+    );
+
+    isNotAlwaysExhaustive(
+      recordTypeNone(
+        positionalTypes: [boolNone, intNone],
+      ),
+    );
+
+    isNotAlwaysExhaustive(
+      recordTypeNone(
+        namedTypes: {
+          'f0': intNone,
+        },
+      ),
+    );
+
+    isNotAlwaysExhaustive(
+      recordTypeNone(
+        namedTypes: {
+          'f0': boolNone,
+          'f1': intNone,
+        },
+      ),
+    );
+  }
+
+  test_typeParameter() {
+    isAlwaysExhaustive(
+      typeParameterTypeNone(
+        typeParameter('T', bound: boolNone),
+      ),
+    );
+
+    isNotAlwaysExhaustive(
+      typeParameterTypeNone(
+        typeParameter('T', bound: numNone),
+      ),
+    );
+
+    isAlwaysExhaustive(
+      typeParameterTypeNone(
+        typeParameter('T'),
+        promotedBound: boolNone,
+      ),
+    );
+
+    isNotAlwaysExhaustive(
+      typeParameterTypeNone(
+        typeParameter('T'),
+        promotedBound: intNone,
+      ),
+    );
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/element/test_all.dart b/pkg/analyzer/test/src/dart/element/test_all.dart
index 8d78853..b3161cc 100644
--- a/pkg/analyzer/test/src/dart/element/test_all.dart
+++ b/pkg/analyzer/test/src/dart/element/test_all.dart
@@ -4,6 +4,7 @@
 
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
+import 'always_exhaustive_test.dart' as always_exhaustive;
 import 'class_element_test.dart' as class_element;
 import 'class_hierarchy_test.dart' as class_hierarchy;
 import 'display_string_test.dart' as display_string;
@@ -36,6 +37,7 @@
 /// Utility for manually running all tests.
 main() {
   defineReflectiveSuite(() {
+    always_exhaustive.main();
     class_element.main();
     class_hierarchy.main();
     display_string.main();