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();