[_fe_analyzer_shared] Improve exhaustiveness for null-assert

This improves the exhaustiveness handling for null assert patterns
by extending the space of the subpattern with the null space. This
reflects the fact that null assert will throw on `null` and can
therefore be considered to cover that case.

Change-Id: If344eaaa55dcc70474f45519b53586e7d36e6e80
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286400
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart
index bc803a8..b9ce4d2 100644
--- a/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart
+++ b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart
@@ -8,23 +8,29 @@
   A(this.field);
 }
 
+sealed class B {}
+class C extends B {}
+class D extends B {}
+
 simpleAssert(o1, o2) {
   var a = /*
    fields={},
    subtypes={Object,Null},
    type=Object?
   */switch (o1) {
-    _! /*space=??*/=> 0,
-    _ /*space=()*/=> 1
+    _! /*space=()*/=> 0,
+    _ /*
+     error=unreachable,
+     space=()
+    */=> 1
   };
 
   var b = /*
-   error=non-exhaustive:Object,
    fields={},
    subtypes={Object,Null},
    type=Object?
   */switch (o2) {
-    _! /*space=??*/=> 0,
+    _! /*space=()*/=> 0,
   };
 }
 
@@ -36,7 +42,7 @@
    subtypes={Object,Null},
    type=Object?
   */switch (o1) {
-    A(field: 42)! /*space=??*/=> 0,
+    A(field: 42)! /*cfe.space=A(field: IntConstant(42))|Null*//*analyzer.space=A(field: int (42))|Null*/=> 0,
     _ /*space=()*/=> 1
   };
 
@@ -46,6 +52,103 @@
    subtypes={Object,Null},
    type=Object?
   */switch (o2) {
-    A(field: 42)! /*space=??*/=> 0,
+    A(field: 42)! /*cfe.space=A(field: IntConstant(42))|Null*//*analyzer.space=A(field: int (42))|Null*/=> 0,
   };
-}
\ No newline at end of file
+}
+
+nullableBool(bool? b1, bool? b2) {
+  /*
+   expandedSubtypes={true,false,Null},
+   fields={},
+   subtypes={bool,Null},
+   type=bool?
+  */
+  switch (b1) {
+    /*space=true?*/
+    case true!:
+      break;
+    /*space=false*/
+    case false:
+      break;
+  }
+  /*
+   error=non-exhaustive:false,
+   expandedSubtypes={true,false,Null},
+   fields={},
+   subtypes={bool,Null},
+   type=bool?
+  */
+  switch (b2) {
+    /*space=true?*/
+    case true!:
+      break;
+  }
+}
+
+nullableA(A? a1, A? a2, A? a3) {
+  var a = /*
+   fields={},
+   subtypes={A,Null},
+   type=A?
+  */switch (a1) {
+    A()! /*space=A?*/=> 0,
+  };
+  var b = /*
+   fields={},
+   subtypes={A,Null},
+   type=A?
+  */switch (a2) {
+    A(:var field)! /*space=A(field: int)|Null*/=> 0,
+  };
+  var c = /*
+   error=non-exhaustive:A(field: int),
+   fields={},
+   subtypes={A,Null},
+   type=A?
+  */switch (a3) {
+    A(field: 42)!
+      /*cfe.space=A(field: IntConstant(42))|Null*/
+      /*analyzer.space=A(field: int (42))|Null*/
+      => 0,
+  };
+}
+
+nullableB(B? b1, B? b2, B? b3) {
+  /*
+   expandedSubtypes={C,D,Null},
+   fields={},
+   subtypes={B,Null},
+   type=B?
+  */
+  switch (b1) {
+    /*space=B?*/
+    case B()!:
+      break;
+  }
+  /*
+   expandedSubtypes={C,D,Null},
+   fields={},
+   subtypes={B,Null},
+   type=B?
+  */
+  switch (b2) {
+    /*space=C?*/
+    case C()!:
+      break;
+    /*space=D*/
+    case D():
+      break;
+  }
+  /*
+   error=non-exhaustive:D,
+   expandedSubtypes={C,D,Null},
+   fields={},
+   subtypes={B,Null},
+   type=B?
+  */
+  switch (b3) {
+    /*space=C?*/
+    case C()!:
+      break;
+  }
+}
diff --git a/pkg/analyzer/lib/src/generated/exhaustiveness.dart b/pkg/analyzer/lib/src/generated/exhaustiveness.dart
index 37f7e43..72d5136 100644
--- a/pkg/analyzer/lib/src/generated/exhaustiveness.dart
+++ b/pkg/analyzer/lib/src/generated/exhaustiveness.dart
@@ -380,8 +380,10 @@
       return convertPattern(pattern.pattern, nonNull: true);
     } else if (pattern is ParenthesizedPattern) {
       return convertPattern(pattern.pattern, nonNull: nonNull);
-    } else if (pattern is NullAssertPattern ||
-        pattern is CastPattern ||
+    } else if (pattern is NullAssertPattern) {
+      Space space = convertPattern(pattern.pattern, nonNull: true);
+      return Space.union([space, Space.nullSpace]);
+    } else if (pattern is CastPattern ||
         pattern is RelationalPattern ||
         pattern is LogicalAndPattern) {
       // These pattern do not add to the exhaustiveness coverage.
diff --git a/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart b/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart
index f7d7505..985fbbc 100644
--- a/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart
@@ -473,8 +473,12 @@
   } else if (pattern is NullCheckPattern) {
     return convertPatternToSpace(cache, pattern.pattern, constants, context,
         nonNull: true);
-  } else if (pattern is NullAssertPattern ||
-      pattern is CastPattern ||
+  } else if (pattern is NullAssertPattern) {
+    Space space = convertPatternToSpace(
+        cache, pattern.pattern, constants, context,
+        nonNull: true);
+    return new Space.union([space, Space.nullSpace]);
+  } else if (pattern is CastPattern ||
       pattern is InvalidPattern ||
       pattern is RelationalPattern ||
       pattern is AndPattern) {