Flow analysis: allow `identical` to promote values to non-nullable.

This change causes `identical(x, y)` to be treated similarly to `x ==
y` for flow analysis.  For example:

    int? x = ...;
    if (!identical(x, null)) {
      print(x + 1); // Ok, x is known not to be null.
    }

Fixes https://github.com/dart-lang/language/issues/1226.

Bug: https://github.com/dart-lang/language/issues/1226
Change-Id: If2939d3d4dd0dc9d7aaf39984045e9ec3e10ce7d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174760
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical.dart
new file mode 100644
index 0000000..f74d1ec
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, 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.
+
+Null nullExpr = null;
+
+void var_identical_null(int? x) {
+  if (identical(x, null)) {
+    x;
+  } else {
+    /*nonNullable*/ x;
+  }
+}
+
+void var_notIdentical_null(int? x) {
+  if (!identical(x, null)) {
+    /*nonNullable*/ x;
+  } else {
+    x;
+  }
+}
+
+void null_identical_var(int? x) {
+  if (identical(null, x)) {
+    x;
+  } else {
+    /*nonNullable*/ x;
+  }
+}
+
+void null_notIdentical_var(int? x) {
+  if (!identical(null, x)) {
+    /*nonNullable*/ x;
+  } else {
+    x;
+  }
+}
+
+void var_identical_nullExpr(int? x) {
+  if (identical(x, nullExpr)) {
+    x;
+  } else {
+    x;
+  }
+}
+
+void var_notIdentical_nullExpr(int? x) {
+  if (!identical(x, nullExpr)) {
+    x;
+  } else {
+    x;
+  }
+}
+
+void nullExpr_identical_var(int? x) {
+  if (identical(nullExpr, x)) {
+    x;
+  } else {
+    x;
+  }
+}
+
+void nullExpr_notIdentical_var(int? x) {
+  if (!identical(nullExpr, x)) {
+    x;
+  } else {
+    x;
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical_prefixed.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical_prefixed.dart
new file mode 100644
index 0000000..febe994
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical_prefixed.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.
+
+import 'dart:core';
+import 'dart:core' as core;
+
+void test(int? x) {
+  if (core.identical(x, null)) {
+    x;
+  } else {
+    /*nonNullable*/ x;
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical_spoof.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical_spoof.dart
new file mode 100644
index 0000000..1fdc9fe
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data/identical_spoof.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, 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.
+
+bool identical(Object? x, Object? y) => false;
+
+void test(int? x) {
+  if (identical(x, null)) {
+    x;
+  } else {
+    x;
+  }
+}
diff --git a/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart b/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart
index 1b4b7a0..a07074f 100644
--- a/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart
@@ -312,8 +312,19 @@
     return typeArgs;
   }
 
+  bool _isCallToIdentical(AstNode invocation) {
+    if (invocation is MethodInvocation) {
+      var invokedMethod = invocation.methodName.staticElement;
+      return invokedMethod != null &&
+          invokedMethod.name == 'identical' &&
+          invokedMethod.library.isDartCore;
+    }
+    return false;
+  }
+
   void _resolveArguments(ArgumentList argumentList) {
-    argumentList.accept(_resolver);
+    _resolver.visitArgumentList(argumentList,
+        isIdentical: _isCallToIdentical(argumentList.parent));
   }
 
   void _resolveInvocation({
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 56ed8d4..b3a30c8 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -788,8 +788,9 @@
   }
 
   @override
-  void visitArgumentList(ArgumentList node) {
+  void visitArgumentList(ArgumentList node, {bool isIdentical = false}) {
     DartType callerType = InferenceContext.getContext(node);
+    NodeList<Expression> arguments = node.arguments;
     if (callerType is FunctionType) {
       Map<String, DartType> namedParameterTypes =
           callerType.namedParameterTypes;
@@ -798,7 +799,6 @@
       int normalCount = normalParameterTypes.length;
       int optionalCount = optionalParameterTypes.length;
 
-      NodeList<Expression> arguments = node.arguments;
       Iterable<Expression> positional =
           arguments.takeWhile((l) => l is! NamedExpression);
       Iterable<Expression> required = positional.take(normalCount);
@@ -840,7 +840,23 @@
         }
       }
     }
-    super.visitArgumentList(node);
+    checkUnreachableNode(node);
+    int length = arguments.length;
+    for (var i = 0; i < length; i++) {
+      if (isIdentical && length > 1 && i == 1) {
+        var firstArg = arguments[0];
+        _flowAnalysis?.flow
+            ?.equalityOp_rightBegin(firstArg, firstArg.staticType);
+      }
+      arguments[i].accept(this);
+    }
+    if (isIdentical && length > 1) {
+      var secondArg = arguments[1];
+      _flowAnalysis?.flow
+          ?.equalityOp_end(node.parent, secondArg, secondArg.staticType);
+    }
+    node.accept(elementResolver);
+    node.accept(typeAnalyzer);
   }
 
   @override
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
index 43f7c3d..2b1cf58 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
@@ -2004,6 +2004,11 @@
           new List<DartType>.filled(
               calleeTypeParameters.length, const DynamicType()));
     }
+    TreeNode parent = arguments.parent;
+    bool isIdentical = arguments.positional.length == 2 &&
+        parent is StaticInvocation &&
+        parent.target.name.name == 'identical' &&
+        parent.target.parent == typeSchemaEnvironment.coreTypes.coreLibrary;
     // TODO(paulberry): if we are doing top level inference and type arguments
     // were omitted, report an error.
     for (int position = 0; position < arguments.positional.length; position++) {
@@ -2044,6 +2049,14 @@
             : legacyErasure(result.inferredType);
         Expression expression =
             _hoist(result.expression, inferredType, hoistedExpressions);
+        if (isIdentical && arguments.positional.length == 2) {
+          if (position == 0) {
+            flowAnalysis?.equalityOp_rightBegin(expression, inferredType);
+          } else {
+            flowAnalysis?.equalityOp_end(
+                arguments.parent, expression, inferredType);
+          }
+        }
         arguments.positional[position] = expression..parent = arguments;
       }
       if (inferenceNeeded || typeChecksNeeded) {