[dart2js] Use exactOrEmpty in RecordTypeMask.toFlatTypeMask

When a record shape is uninstantiated in the closed world,
RecordTypeMask.toFlatTypeMask falls back to the record arity class
(e.g. _Record3). If no record literals are instantiated anywhere in the
program, the arity class itself is also uninstantiated and has no strict
subclasses.

Using TypeMask.exactOrEmpty / TypeMask.nonNullExactOrEmpty ensures that
an uninstantiated record arity class normalizes to an empty mask instead
of triggering an assertion failure in TypeMask.assertIsNormalized.

Also preserve special values (null, lateSentinel) in
RecordTypeMask.createRecordWithPowerset when any field type is empty.

Fixes https://github.com/dart-lang/sdk/issues/64100

TAG=agy
CONV=bca542cb-694d-48a1-baac-322aa1508f0c

Change-Id: Icd22328a35481d2af7e179a5e98c59d5163d05a6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/549040
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/record_type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/record_type_mask.dart
index a20eaf7..6c115e4 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/record_type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/record_type_mask.dart
@@ -58,7 +58,10 @@
     // If any field is empty then this record is not instantiable and we
     // simplify to an empty mask.
     if (types.any((e) => e.isEmpty)) {
-      return domain.emptyType;
+      return FlatTypeMask._emptyOrSpecial(
+        domain,
+        _specialValueDomain.restrict(powerset),
+      );
     }
     return RecordTypeMask._(types, shape, powerset);
   }
@@ -383,17 +386,18 @@
               hasLateSentinel: hasLateSentinel,
             );
     } else {
-      return isNullable
-          ? FlatTypeMask.exact(
-              recordClass,
-              domain,
-              hasLateSentinel: hasLateSentinel,
-            )
-          : FlatTypeMask.nonNullExact(
-              recordClass,
-              domain,
-              hasLateSentinel: hasLateSentinel,
-            );
+      return (isNullable
+              ? TypeMask.exactOrEmpty(
+                  recordClass,
+                  domain,
+                  hasLateSentinel: hasLateSentinel,
+                )
+              : TypeMask.nonNullExactOrEmpty(
+                  recordClass,
+                  domain,
+                  hasLateSentinel: hasLateSentinel,
+                ))
+          as FlatTypeMask;
     }
   }
 
diff --git a/pkg/compiler/test/inference/record_type_test.dart b/pkg/compiler/test/inference/record_type_test.dart
index c587164..e259ac1 100644
--- a/pkg/compiler/test/inference/record_type_test.dart
+++ b/pkg/compiler/test/inference/record_type_test.dart
@@ -1034,8 +1034,37 @@
     Expect.isFalse(recordAMask.nullable(domain).isNull);
   }
 
+  runNoRecordsInstantiatedTest() async {
+    TypeEnvironment env = await TypeEnvironment.create(r"""
+      class A {}
+      main() {
+        print(A());
+      }
+      """, testBackendWorld: true);
+    JClosedWorld world = env.jClosedWorld;
+    final domain = world.abstractValueDomain as CommonMasks;
+    final aMask = FlatTypeMask.nonNullExact(env.getClass('A'), domain);
+    final shape3 = RecordShape(2, ["bar"]);
+    final uninstantiatedRecordMask = RecordTypeMask.createRecord(domain, [
+      aMask,
+      aMask,
+      aMask,
+    ], shape3) as RecordTypeMask;
+    Expect.equals(
+      domain.emptyType,
+      uninstantiatedRecordMask.toFlatTypeMask(domain),
+    );
+    Expect.equals(
+      domain.nullType,
+      (uninstantiatedRecordMask.nullable(domain) as RecordTypeMask)
+          .toFlatTypeMask(domain),
+    );
+    Expect.equals(aMask, uninstantiatedRecordMask.union(aMask, domain));
+  }
+
   asyncTest(() async {
     print('--test from kernel------------------------------------------------');
     await runTest();
+    await runNoRecordsInstantiatedTest();
   });
 }