[mini_types] Add an assertion to verify the runtime type of `PrimaryType`-derived objects.

The class hierarchy for `PrimaryType` has several subtypes to
represent special types in Dart:

- DynamicType (for `dynamic`)
- FutureOrType (for `FutureOr<...>`)
- InvalidType (for the invalid type)
- NeverType (for `Never`)
- NullType (for `Null`)
- VoidType (for `Void`)

When constructing a `PrimaryType` for one of these special types, we
need to make sure that the type that's being ultimately constructed is
the proper subtype, otherwise algorithms that do `is` tests on the
`Type` hierarchy will behave incorrectly.

This change adds an assertion to the `PrimaryType` constructor to
verify that the appropriate subtype of `Type` is being constructed.

Change-Id: I347cc4893da265b35e97636479700a92a8e61541
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/395560
Reviewed-by: Kallen Tu <kallentu@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/mini_types.dart b/pkg/_fe_analyzer_shared/test/mini_types.dart
index 6190ad6..1d8ba61 100644
--- a/pkg/_fe_analyzer_shared/test/mini_types.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_types.dart
@@ -6,6 +6,9 @@
 // but light weight enough to be suitable for unit testing of code in the
 // `_fe_analyzer_shared` package.
 
+import 'dart:core' as core show Type;
+import 'dart:core' hide Type;
+
 import 'package:_fe_analyzer_shared/src/type_inference/nullability_suffix.dart';
 import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
 
@@ -163,7 +166,7 @@
 
 /// A type name that represents an ordinary interface type.
 class InterfaceTypeName extends TypeNameInfo {
-  InterfaceTypeName._(super.name);
+  InterfaceTypeName._(super.name) : super(expectedRuntimeType: PrimaryType);
 }
 
 /// Representation of an invalid type suitable for unit testing of code in the
@@ -261,7 +264,12 @@
 
   PrimaryType._(this.nameInfo,
       {this.args = const [], super.nullabilitySuffix = NullabilitySuffix.none})
-      : super._();
+      : super._() {
+    assert(
+        runtimeType == nameInfo._expectedRuntimeType,
+        '${nameInfo.name} should use ${nameInfo._expectedRuntimeType}, but '
+        'constructed $runtimeType instead');
+  }
 
   PrimaryType._special(SpecialTypeName nameInfo,
       {List<Type> args = const [],
@@ -427,7 +435,7 @@
 /// - `Null`
 /// - `void`
 class SpecialTypeName extends TypeNameInfo {
-  SpecialTypeName._(super.name);
+  SpecialTypeName._(super.name, {required super.expectedRuntimeType});
 }
 
 /// Representation of a type suitable for unit testing of code in the
@@ -512,7 +520,21 @@
 sealed class TypeNameInfo {
   final String name;
 
-  TypeNameInfo(this.name);
+  /// The runtime type that should be used for [Type] objects that refer to
+  /// `this`.
+  ///
+  /// An assertion in the [PrimaryType] constructor verifies this.
+  ///
+  /// This ensures that the methods [Type.closureWithRespectToUnknown],
+  /// [Type.recursivelyDemote], and [Type.withNullability] (which create new
+  /// instances of [Type] based on old ones) create the appropriate subtype of
+  /// [Type]. It also ensures that when [Type] objects are directly constructed
+  /// (as they are in this file and in `mini_ast.dart`), the appropriate subtype
+  /// of [Type] is used.
+  final core.Type _expectedRuntimeType;
+
+  TypeNameInfo(this.name, {required core.Type expectedRuntimeType})
+      : _expectedRuntimeType = expectedRuntimeType;
 }
 
 /// A type name that represents a type variable.
@@ -522,7 +544,9 @@
   @override
   Type bound;
 
-  TypeParameter._(super.name) : bound = Type('Object?');
+  TypeParameter._(super.name)
+      : bound = Type('Object?'),
+        super(expectedRuntimeType: TypeParameterType);
 
   @override
   String get displayName => name;
@@ -607,16 +631,19 @@
   static Map<String, TypeNameInfo>? _typeNameInfoMap;
 
   /// The [TypeNameInfo] object representing the special type `dynamic`.
-  static final dynamic_ = SpecialTypeName._('dynamic');
+  static final dynamic_ =
+      SpecialTypeName._('dynamic', expectedRuntimeType: DynamicType);
 
   /// The [TypeNameInfo] object representing the special type `error`.
-  static final error_ = SpecialTypeName._('error');
+  static final error_ =
+      SpecialTypeName._('error', expectedRuntimeType: InvalidType);
 
   /// The [TypeNameInfo] object representing the interface type `Future`.
   static final future = InterfaceTypeName._('Future');
 
   /// The [TypeNameInfo] object representing the special type `FutureOr`.
-  static final futureOr = SpecialTypeName._('FutureOr');
+  static final futureOr =
+      SpecialTypeName._('FutureOr', expectedRuntimeType: FutureOrType);
 
   /// The [TypeNameInfo] object representing the interface type `Iterable`.
   static final iterable = InterfaceTypeName._('Iterable');
@@ -628,16 +655,17 @@
   static final map = InterfaceTypeName._('Map');
 
   /// The [TypeNameInfo] object representing the special type `Never`.
-  static final never = SpecialTypeName._('Never');
+  static final never =
+      SpecialTypeName._('Never', expectedRuntimeType: NeverType);
 
   /// The [TypeNameInfo] object representing the special type `Null`.
-  static final null_ = SpecialTypeName._('Null');
+  static final null_ = SpecialTypeName._('Null', expectedRuntimeType: NullType);
 
   /// The [TypeNameInfo] object representing the interface type `Stream`.
   static final stream = InterfaceTypeName._('Stream');
 
   /// The [TypeNameInfo] object representing the special type `void`.
-  static final void_ = SpecialTypeName._('void');
+  static final void_ = SpecialTypeName._('void', expectedRuntimeType: VoidType);
 
   /// Gets [_typeNameInfoMap], throwing an exception if it has not been
   /// initialized.