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) {