Fix handling of &&, || and ?? operators

Change-Id: Ibfe574f381af701932cf30f5fdd77fe50a7c8158
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106961
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/nnbd_migration/lib/src/edge_origin.dart b/pkg/nnbd_migration/lib/src/edge_origin.dart
index 487edc1..4b38fa7 100644
--- a/pkg/nnbd_migration/lib/src/edge_origin.dart
+++ b/pkg/nnbd_migration/lib/src/edge_origin.dart
@@ -34,6 +34,11 @@
   EdgeOriginWithLocation(this.source, this.offset);
 }
 
+/// Edge origin resulting from the presence of a `??` operator.
+class IfNullOrigin extends EdgeOriginWithLocation {
+  IfNullOrigin(Source source, int offset) : super(source, offset);
+}
+
 /// Edge origin resulting from a class that is instantiated to bounds.
 ///
 /// For example, in the following code snippet:
diff --git a/pkg/nnbd_migration/lib/src/graph_builder.dart b/pkg/nnbd_migration/lib/src/graph_builder.dart
index a6942d2..412a3ba 100644
--- a/pkg/nnbd_migration/lib/src/graph_builder.dart
+++ b/pkg/nnbd_migration/lib/src/graph_builder.dart
@@ -215,11 +215,26 @@
       }
       return _nonNullableBoolType;
     } else if (operatorType == TokenType.AMPERSAND_AMPERSAND ||
-        operatorType == TokenType.BAR_BAR ||
-        operatorType == TokenType.QUESTION_QUESTION) {
+        operatorType == TokenType.BAR_BAR) {
       _handleAssignment(_notNullType, node.leftOperand);
-      node.rightOperand.accept(this);
+      _handleAssignment(_notNullType, node.rightOperand);
       return _nonNullableBoolType;
+    } else if (operatorType == TokenType.QUESTION_QUESTION) {
+      DecoratedType expressionType;
+      var leftType = node.leftOperand.accept(this);
+      try {
+        _guards.add(leftType.node);
+        var rightType = node.rightOperand.accept(this);
+        var ifNullNode = NullabilityNode.forIfNotNull();
+        expressionType = DecoratedType(node.staticType, ifNullNode);
+        _graph.connect(rightType.node, expressionType.node,
+            IfNullOrigin(_source, node.offset),
+            guards: _guards);
+      } finally {
+        _guards.removeLast();
+      }
+      _variables.recordDecoratedExpressionType(node, expressionType);
+      return expressionType;
     } else if (operatorType.isUserDefinableOperator) {
       _handleAssignment(_notNullType, node.leftOperand);
       var callee = node.staticElement;
diff --git a/pkg/nnbd_migration/lib/src/nullability_node.dart b/pkg/nnbd_migration/lib/src/nullability_node.dart
index 6e02eb2..6e7d02d 100644
--- a/pkg/nnbd_migration/lib/src/nullability_node.dart
+++ b/pkg/nnbd_migration/lib/src/nullability_node.dart
@@ -302,6 +302,11 @@
   final _upstreamEdges = <NullabilityEdge>[];
 
   /// Creates a [NullabilityNode] representing the nullability of a variable
+  /// whose type is determined by the `??` operator.
+  factory NullabilityNode.forIfNotNull() =>
+      _NullabilityNodeSimple('?? operator');
+
+  /// Creates a [NullabilityNode] representing the nullability of a variable
   /// whose type is `dynamic` due to type inference.
   ///
   /// TODO(paulberry): this should go away; we should decorate the actual
diff --git a/pkg/nnbd_migration/test/graph_builder_test.dart b/pkg/nnbd_migration/test/graph_builder_test.dart
index d9a7a43..7835703 100644
--- a/pkg/nnbd_migration/test/graph_builder_test.dart
+++ b/pkg/nnbd_migration/test/graph_builder_test.dart
@@ -479,7 +479,10 @@
 int f(int i, int j) => i ?? j;
 ''');
 
-    assertNoUpstreamNullability(decoratedTypeAnnotation('int i').node);
+    var left = decoratedTypeAnnotation('int i').node;
+    var right = decoratedTypeAnnotation('int j').node;
+    var expression = decoratedExpressionType('??').node;
+    assertEdge(right, expression, guards: [left], hard: false);
   }
 
   test_binaryExpression_slash_result_not_null() async {