Flow analysis: unit test that join variables are usable and promotable.

These tests fill a coverage gap in the flow analysis unit
tests. Previously we tested that "join" variables were created by
logical-or patterns and switch cases that share a body, but we didn't
have any tests to verify that those variables could be used.

We now verify that those variables can be read from and promoted.

Change-Id: Ic8948cd307edff429aea9007183d65cc3a770ef3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/322360
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
index 40935d9..9914372 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -7882,6 +7882,22 @@
                 ]),
           ]);
         });
+
+        test('Join variable is promotable', () {
+          var x1 = Var('x', identity: 'x1');
+          var x2 = Var('x', identity: 'x2');
+          var x = PatternVariableJoin('x', expectedComponents: [x1, x2]);
+          h.run([
+            ifCase(
+                expr('int?'),
+                x1.pattern(type: 'int?').nullCheck.or(x2.pattern(type: 'int?')),
+                [
+                  checkNotPromoted(x),
+                  x.nonNullAssert,
+                  checkPromoted(x, 'int'),
+                ]),
+          ]);
+        });
       });
 
       group(
@@ -7894,9 +7910,13 @@
           // it's not actually assigned on both sides of the or-pattern) because
           // this avoids redundant errors.
           h.run([
-            ifCase(expr('int?'),
+            ifCase(expr('num?'),
                 (x1.pattern().nullCheck.or(wildcard()))..errorId = 'OR', [
               checkAssigned(x, true),
+              // Also verify that the join variable is promotable
+              checkNotPromoted(x),
+              x.as_('int'),
+              checkPromoted(x, 'int'),
             ]),
           ], expectedErrors: {
             'logicalOrPatternBranchMissingVariable(node: OR, hasInLeft: true, '
@@ -7914,6 +7934,10 @@
             ifCase(expr('int?'),
                 (wildcard().nullCheck.or(x1.pattern()))..errorId = 'OR', [
               checkAssigned(x, true),
+              // Also verify that the join variable is promotable
+              checkNotPromoted(x),
+              x.nonNullAssert,
+              checkPromoted(x, 'int'),
             ]),
           ], expectedErrors: {
             'logicalOrPatternBranchMissingVariable(node: OR, hasInLeft: false, '
@@ -9783,6 +9807,24 @@
             ]),
           ]);
         });
+
+        test('Join variable is promotable', () {
+          var x1 = Var('x', identity: 'x1');
+          var x2 = Var('x', identity: 'x2');
+          var x = PatternVariableJoin('x', expectedComponents: [x1, x2]);
+          h.run([
+            switch_(expr('int?'), [
+              switchStatementMember([
+                x1.pattern(type: 'int?').nullCheck,
+                x2.pattern(type: 'int?')
+              ], [
+                checkNotPromoted(x),
+                x.nonNullAssert,
+                checkPromoted(x, 'int'),
+              ]),
+            ])
+          ]);
+        });
       });
 
       group(
@@ -9795,12 +9837,16 @@
           // not actually assigned by both patterns) because this avoids
           // redundant errors.
           h.run([
-            switch_(expr('int?'), [
+            switch_(expr('num?'), [
               switchStatementMember([
                 x1.pattern().nullCheck,
                 wildcard()
               ], [
                 checkAssigned(x, true),
+                // Also verify that the join variable is promotable
+                checkNotPromoted(x),
+                x.as_('int'),
+                checkPromoted(x, 'int'),
               ])
             ]),
           ]);
@@ -9819,6 +9865,10 @@
                 x1.pattern()
               ], [
                 checkAssigned(x, true),
+                // Also verify that the join variable is promotable
+                checkNotPromoted(x),
+                x.nonNullAssert,
+                checkPromoted(x, 'int'),
               ])
             ]),
           ]);
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 9ee37b7..46c619d 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -1208,7 +1208,7 @@
 
   @override
   void preVisit(PreVisitor visitor) {
-    var variableBinder = _VariableBinder(errors: visitor.errors);
+    var variableBinder = _VariableBinder(visitor);
     variableBinder.casePatternStart();
     pattern.preVisit(visitor, variableBinder, isInAssignment: false);
     variableBinder.casePatternFinish();
@@ -1395,7 +1395,7 @@
   void _preVisit(PreVisitor visitor) {
     final guardedPattern = this.guardedPattern;
     if (guardedPattern != null) {
-      var variableBinder = _VariableBinder(errors: visitor.errors);
+      var variableBinder = _VariableBinder(visitor);
       variableBinder.casePatternStart();
       guardedPattern.pattern
           .preVisit(visitor, variableBinder, isInAssignment: false);
@@ -1904,7 +1904,7 @@
   @override
   void preVisit(PreVisitor visitor) {
     expression.preVisit(visitor);
-    var variableBinder = _VariableBinder(errors: visitor.errors);
+    var variableBinder = _VariableBinder(visitor);
     variableBinder.casePatternStart();
     pattern.preVisit(visitor, variableBinder, isInAssignment: false);
     _candidateVariables = variableBinder.casePatternFinish();
@@ -1950,7 +1950,7 @@
   @override
   void preVisit(PreVisitor visitor) {
     expression.preVisit(visitor);
-    var variableBinder = _VariableBinder(errors: visitor.errors);
+    var variableBinder = _VariableBinder(visitor);
     variableBinder.casePatternStart();
     pattern.preVisit(visitor, variableBinder, isInAssignment: false);
     _variables = variableBinder.casePatternFinish();
@@ -2581,6 +2581,7 @@
     'List<int>': false,
     'Never': false,
     'num': false,
+    'num?': false,
     'Object': false,
     'Object?': false,
     'String': false,
@@ -3235,7 +3236,7 @@
 
   @override
   void preVisit(PreVisitor visitor) {
-    var variableBinder = _VariableBinder(errors: visitor.errors);
+    var variableBinder = _VariableBinder(visitor);
     variableBinder.casePatternStart();
     lhs.preVisit(visitor, variableBinder, isInAssignment: true);
     variableBinder.casePatternFinish();
@@ -3266,7 +3267,7 @@
   void preVisit(PreVisitor visitor) {
     expression.preVisit(visitor);
 
-    var variableBinder = _VariableBinder(errors: visitor.errors);
+    var variableBinder = _VariableBinder(visitor);
     variableBinder.casePatternStart();
     pattern.preVisit(visitor, variableBinder, isInAssignment: false);
     variableBinder.casePatternFinish();
@@ -3314,7 +3315,7 @@
   void preVisit(PreVisitor visitor) {
     expression.preVisit(visitor);
 
-    var variableBinder = _VariableBinder(errors: visitor.errors);
+    var variableBinder = _VariableBinder(visitor);
     variableBinder.casePatternStart();
     pattern.preVisit(visitor, variableBinder, isInAssignment: false);
     variableBinder.casePatternFinish();
@@ -3401,6 +3402,7 @@
   void _handleJoin({
     required List<Var> components,
     required JoinedPatternVariableInconsistency inconsistency,
+    required PreVisitor visitor,
   }) {
     expect(isJoined, false);
     expect(components.map((c) => c.identity),
@@ -3409,6 +3411,7 @@
     expect(components, expectedComponents, reason: 'at $location');
     this.inconsistency = inconsistency;
     this.isJoined = true;
+    visitor._assignedVariables.declare(this);
   }
 }
 
@@ -4192,7 +4195,7 @@
   }) : super._();
 
   void _preVisit(PreVisitor visitor) {
-    var variableBinder = _VariableBinder(errors: visitor.errors);
+    var variableBinder = _VariableBinder(visitor);
     variableBinder.switchStatementSharedCaseScopeStart(this);
     for (SwitchHead element in elements) {
       if (element is SwitchHeadCase) {
@@ -5863,9 +5866,9 @@
 }
 
 class _VariableBinder extends VariableBinder<Node, Var> {
-  _VariableBinder({
-    required super.errors,
-  });
+  final PreVisitor visitor;
+
+  _VariableBinder(this.visitor) : super(errors: visitor.errors);
 
   @override
   Var joinPatternVariables({
@@ -5880,6 +5883,7 @@
     joinedVariable._handleJoin(
       components: components,
       inconsistency: inconsistency,
+      visitor: visitor,
     );
     return joinedVariable;
   }