Resolve x ?? y as LUB(X!, Y) instead of LUB(X, Y).

Change-Id: I081d8ec153916fb37e2a949d9fa85a6ea778438e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/92390
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Mike Fairhurst <mfairhurst@google.com>
Auto-Submit: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index ceca4ee..14f6958 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -71,7 +71,10 @@
    */
   TypePromotionManager _promoteManager;
 
-  bool nonNullableEnabled;
+  /**
+   * Whether NNBD is enabled for this compilation unit.
+   */
+  bool _nonNullableEnabled;
 
   /**
    * Initialize a newly created type analyzer.
@@ -86,7 +89,7 @@
     AnalysisOptionsImpl analysisOptions =
         _resolver.definingLibrary.context.analysisOptions;
     _strictInference = analysisOptions.strictInference;
-    nonNullableEnabled = featureSet.isEnabled(Feature.non_nullable);
+    _nonNullableEnabled = featureSet.isEnabled(Feature.non_nullable);
   }
 
   /**
@@ -288,10 +291,22 @@
       DartType staticType = _getStaticType(rightHandSide);
       _recordStaticType(node, staticType);
     } else if (operator == TokenType.QUESTION_QUESTION_EQ) {
-      // The static type of a compound assignment using ??= is the least upper
-      // bound of the static types of the LHS and RHS.
-      _analyzeLeastUpperBound(node, node.leftHandSide, node.rightHandSide,
-          read: true);
+      if (_nonNullableEnabled) {
+        // The static type of a compound assignment using ??= with NNBD is the
+        // least upper bound of the static types of the LHS and RHS after
+        // promoting the LHS/ to non-null (as we know its value will not be used
+        // if null)
+        _analyzeLeastUpperBoundTypes(
+            node,
+            _typeSystem.promoteToNonNull(
+                _getExpressionType(node.leftHandSide, read: true)),
+            _getExpressionType(node.rightHandSide, read: true));
+      } else {
+        // The static type of a compound assignment using ??= before NNBD is the
+        // least upper bound of the static types of the LHS and RHS.
+        _analyzeLeastUpperBound(node, node.leftHandSide, node.rightHandSide,
+            read: true);
+      }
       return;
     } else if (operator == TokenType.AMPERSAND_AMPERSAND_EQ ||
         operator == TokenType.BAR_BAR_EQ) {
@@ -370,11 +385,23 @@
   @override
   void visitBinaryExpression(BinaryExpression node) {
     if (node.operator.type == TokenType.QUESTION_QUESTION) {
-      // Evaluation of an if-null expression e of the form e1 ?? e2 is
-      // equivalent to the evaluation of the expression
-      // ((x) => x == null ? e2 : x)(e1).  The static type of e is the least
-      // upper bound of the static type of e1 and the static type of e2.
-      _analyzeLeastUpperBound(node, node.leftOperand, node.rightOperand);
+      if (_nonNullableEnabled) {
+        // The static type of a compound assignment using ??= with NNBD is the
+        // least upper bound of the static types of the LHS and RHS after
+        // promoting the LHS/ to non-null (as we know its value will not be used
+        // if null)
+        _analyzeLeastUpperBoundTypes(
+            node,
+            _typeSystem.promoteToNonNull(
+                _getExpressionType(node.leftOperand, read: true)),
+            _getExpressionType(node.rightOperand, read: true));
+      } else {
+        // Without NNBD, evaluation of an if-null expression e of the form
+        // e1 ?? e2 is equivalent to the evaluation of the expression
+        // ((x) => x == null ? e2 : x)(e1).  The static type of e is the least
+        // upper bound of the static type of e1 and the static type of e2.
+        _analyzeLeastUpperBound(node, node.leftOperand, node.rightOperand);
+      }
       return;
     }
     DartType staticType = node.staticInvokeType?.returnType ?? _dynamicType;
@@ -874,7 +901,8 @@
       // TODO(brianwilkerson) Report this internal error.
     }
 
-    if (node.operator.type == TokenType.QUESTION_PERIOD && nonNullableEnabled) {
+    if (node.operator.type == TokenType.QUESTION_PERIOD &&
+        _nonNullableEnabled) {
       staticType =
           (staticType as TypeImpl).withNullability(NullabilitySuffix.question);
     }
@@ -1087,10 +1115,21 @@
       {bool read: false}) {
     DartType staticType1 = _getExpressionType(expr1, read: read);
     DartType staticType2 = _getExpressionType(expr2, read: read);
+
+    _analyzeLeastUpperBoundTypes(node, staticType1, staticType2);
+  }
+
+  /**
+   * Set the static type of [node] to be the least upper bound of the static
+   * types [staticType1] and [staticType2].
+   */
+  void _analyzeLeastUpperBoundTypes(
+      Expression node, DartType staticType1, DartType staticType2) {
     if (staticType1 == null) {
       // TODO(brianwilkerson) Determine whether this can still happen.
       staticType1 = _dynamicType;
     }
+
     if (staticType2 == null) {
       // TODO(brianwilkerson) Determine whether this can still happen.
       staticType2 = _dynamicType;
@@ -1186,7 +1225,7 @@
       returnType = type.returnType;
     }
 
-    if (isNullableInvoke && nonNullableEnabled) {
+    if (isNullableInvoke && _nonNullableEnabled) {
       returnType = returnType?.withNullability(NullabilitySuffix.question);
     }
 
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index 64b747e..a774f85 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -2082,6 +2082,11 @@
     return null;
   }
 
+  DartType promoteToNonNull(TypeImpl type) {
+    // TODO(mfairhurst): handle type parameter types
+    return type.withNullability(NullabilitySuffix.none);
+  }
+
   /**
    * Attempts to make a better guess for the type of a binary with the given
    * [operator], given that resolution has so far produced the [currentType].
diff --git a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
index 089d66d..0011f32 100644
--- a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
@@ -55,6 +55,56 @@
     assertType(findNode.methodInvocation('c?.x()'), 'bool?');
   }
 
+  test_local_nullCoalesce_nullableInt_int() async {
+    await addTestFile(r'''
+m() {
+  int? x;
+  int y;
+  x ?? y;
+}
+''');
+    await resolveTestFile();
+    assertNoTestErrors();
+    assertType(findNode.binary('x ?? y'), 'int');
+  }
+
+  test_local_nullCoalesce_nullableInt_nullableInt() async {
+    await addTestFile(r'''
+m() {
+  int? x;
+  x ?? x;
+}
+''');
+    await resolveTestFile();
+    assertNoTestErrors();
+    assertType(findNode.binary('x ?? x'), 'int?');
+  }
+
+  test_local_nullCoalesceAssign_nullableInt_int() async {
+    await addTestFile(r'''
+m() {
+  int? x;
+  int y;
+  x ??= y;
+}
+''');
+    await resolveTestFile();
+    assertNoTestErrors();
+    assertType(findNode.assignment('x ??= y'), 'int');
+  }
+
+  test_local_nullCoalesceAssign_nullableInt_nullableInt() async {
+    await addTestFile(r'''
+m() {
+  int? x;
+  x ??= x;
+}
+''');
+    await resolveTestFile();
+    assertNoTestErrors();
+    assertType(findNode.assignment('x ??= x'), 'int?');
+  }
+
   test_local_parameter_interfaceType() async {
     addTestFile('''
 main() {
diff --git a/tests/language_2/nnbd/resolution/question_question_lub_test.dart b/tests/language_2/nnbd/resolution/question_question_lub_test.dart
new file mode 100644
index 0000000..1606441
--- /dev/null
+++ b/tests/language_2/nnbd/resolution/question_question_lub_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// SharedOptions=--enable-experiment=non-nullable
+
+// Test that `x ?? y` results in type LUB(x!, y)
+void main() {
+  f1(null, 2);
+}
+
+void f1(
+    int? nullableInt,
+    int nonNullInt,
+) {
+  (nullableInt ?? nonNullInt) + 1; //# 00: ok
+  (nullableInt ?? nullableInt) + 1; //# 01: compile-time error
+  (nonNullInt ?? nullableInt) + 1; //# 02: compile-time error
+  (nonNullInt ?? nonNullInt) + 1; //# 03: ok
+}
+
+// TODO(mfairhurst) add cases with type parameter types