Update nullability for TypeParameterType in tryPromoteToType()

Change-Id: Ifc8c32e3540577437c6448fa943d8cbbfebf0b62
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/196105
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/type_parameter.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/type_parameter.dart
index c818df6..5b091df 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/type_parameter.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/type_parameter.dart
@@ -12,7 +12,7 @@
   void promoteNullable(T? t) {
     T? s;
     if (t is int) {
-      s = /*analyzer.T? & int*/ /*cfe.T & int*/ t;
+      s = /*T & int*/ t;
     }
   }
 
diff --git a/pkg/analyzer/lib/src/dart/element/type_system.dart b/pkg/analyzer/lib/src/dart/element/type_system.dart
index ca7f2f9..6e82d71 100644
--- a/pkg/analyzer/lib/src/dart/element/type_system.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_system.dart
@@ -1506,7 +1506,10 @@
         var declaration = from.element.declaration;
         return TypeParameterTypeImpl(
           element: declaration,
-          nullabilitySuffix: from.nullabilitySuffix,
+          nullabilitySuffix: _promotedTypeParameterTypeNullability(
+            from.nullabilitySuffix,
+            to.nullabilitySuffix,
+          ),
           promotedBound: to,
         );
       }
@@ -1866,6 +1869,35 @@
     recurse(type);
     return result;
   }
+
+  static NullabilitySuffix _promotedTypeParameterTypeNullability(
+    NullabilitySuffix nullabilityOfType,
+    NullabilitySuffix nullabilityOfBound,
+  ) {
+    if (nullabilityOfType == NullabilitySuffix.question &&
+        nullabilityOfBound == NullabilitySuffix.none) {
+      return NullabilitySuffix.none;
+    }
+
+    if (nullabilityOfType == NullabilitySuffix.question &&
+        nullabilityOfBound == NullabilitySuffix.question) {
+      return NullabilitySuffix.question;
+    }
+
+    if (nullabilityOfType == NullabilitySuffix.star &&
+        nullabilityOfBound == NullabilitySuffix.none) {
+      return NullabilitySuffix.star;
+    }
+
+    // Intersection with a non-nullable type always yields a non-nullable type,
+    // as it's the most restrictive kind of types.
+    if (nullabilityOfType == NullabilitySuffix.none ||
+        nullabilityOfBound == NullabilitySuffix.none) {
+      return NullabilitySuffix.none;
+    }
+
+    return NullabilitySuffix.star;
+  }
 }
 
 /// TODO(scheglov) Ask the language team how to deal with it.
diff --git a/pkg/analyzer/test/generated/type_system_test.dart b/pkg/analyzer/test/generated/type_system_test.dart
index 6a50ac8..492236c 100644
--- a/pkg/analyzer/test/generated/type_system_test.dart
+++ b/pkg/analyzer/test/generated/type_system_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/dart/element/type_provider.dart';
 import 'package:analyzer/src/dart/element/element.dart';
@@ -472,22 +473,51 @@
       return typeSystem.tryPromoteToType(to, from) as TypeParameterTypeImpl;
     }
 
-    void check(
-      TypeParameterTypeImpl type,
-      TypeParameterElement expectedElement,
-      DartType expectedBound,
-    ) {
-      expect(type.element, expectedElement);
-      expect(type.promotedBound, expectedBound);
+    void check(TypeParameterTypeImpl type, String expected) {
+      expect(type.getDisplayString(withNullability: true), expected);
     }
 
     var T = typeParameter('T');
-    var T0 = typeParameterTypeNone(T);
+    var T_none = typeParameterTypeNone(T);
+    var T_question = typeParameterTypeQuestion(T);
+    var T_star = typeParameterTypeStar(T);
 
-    var T1 = tryPromote(numNone, T0);
-    check(T1, T, numNone);
+    check(tryPromote(numNone, T_none), 'T & num');
+    check(tryPromote(numQuestion, T_none), 'T & num?');
+    check(tryPromote(numStar, T_none), 'T & num*');
+
+    check(tryPromote(numNone, T_question), 'T & num');
+    check(tryPromote(numQuestion, T_question), 'T? & num?');
+    check(tryPromote(numStar, T_question), 'T* & num*');
+
+    check(tryPromote(numNone, T_star), 'T* & num');
+    check(tryPromote(numQuestion, T_star), 'T* & num?');
+    check(tryPromote(numStar, T_star), 'T* & num*');
+  }
+
+  test_typeParameter_twice() {
+    TypeParameterTypeImpl tryPromote(DartType to, TypeParameterTypeImpl from) {
+      return typeSystem.tryPromoteToType(to, from) as TypeParameterTypeImpl;
+    }
+
+    void check(
+      TypeParameterTypeImpl type,
+      TypeParameterElement element,
+      NullabilitySuffix nullability,
+      DartType promotedBound,
+    ) {
+      expect(type.element, element);
+      expect(type.nullabilitySuffix, nullability);
+      expect(type.promotedBound, promotedBound);
+    }
+
+    var T = typeParameter('T');
+    var T_none = typeParameterTypeNone(T);
+
+    var T1 = tryPromote(numNone, T_none);
+    check(T1, T, NullabilitySuffix.none, numNone);
 
     var T2 = tryPromote(intNone, T1);
-    check(T2, T, intNone);
+    check(T2, T, NullabilitySuffix.none, intNone);
   }
 }