Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 6a097ed

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
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 dart-lang/language#1226. Bug: dart-lang/language#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>
1 parent f3fe9dc commit 6a097ed

File tree

6 files changed

+140
-4
lines changed

6 files changed

+140
-4
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
Null nullExpr = null;
6+
7+
void var_identical_null(int? x) {
8+
if (identical(x, null)) {
9+
x;
10+
} else {
11+
/*nonNullable*/ x;
12+
}
13+
}
14+
15+
void var_notIdentical_null(int? x) {
16+
if (!identical(x, null)) {
17+
/*nonNullable*/ x;
18+
} else {
19+
x;
20+
}
21+
}
22+
23+
void null_identical_var(int? x) {
24+
if (identical(null, x)) {
25+
x;
26+
} else {
27+
/*nonNullable*/ x;
28+
}
29+
}
30+
31+
void null_notIdentical_var(int? x) {
32+
if (!identical(null, x)) {
33+
/*nonNullable*/ x;
34+
} else {
35+
x;
36+
}
37+
}
38+
39+
void var_identical_nullExpr(int? x) {
40+
if (identical(x, nullExpr)) {
41+
x;
42+
} else {
43+
x;
44+
}
45+
}
46+
47+
void var_notIdentical_nullExpr(int? x) {
48+
if (!identical(x, nullExpr)) {
49+
x;
50+
} else {
51+
x;
52+
}
53+
}
54+
55+
void nullExpr_identical_var(int? x) {
56+
if (identical(nullExpr, x)) {
57+
x;
58+
} else {
59+
x;
60+
}
61+
}
62+
63+
void nullExpr_notIdentical_var(int? x) {
64+
if (!identical(nullExpr, x)) {
65+
x;
66+
} else {
67+
x;
68+
}
69+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:core';
6+
import 'dart:core' as core;
7+
8+
void test(int? x) {
9+
if (core.identical(x, null)) {
10+
x;
11+
} else {
12+
/*nonNullable*/ x;
13+
}
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
bool identical(Object? x, Object? y) => false;
6+
7+
void test(int? x) {
8+
if (identical(x, null)) {
9+
x;
10+
} else {
11+
x;
12+
}
13+
}

pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,19 @@ class InvocationInferenceHelper {
312312
return typeArgs;
313313
}
314314

315+
bool _isCallToIdentical(AstNode invocation) {
316+
if (invocation is MethodInvocation) {
317+
var invokedMethod = invocation.methodName.staticElement;
318+
return invokedMethod != null &&
319+
invokedMethod.name == 'identical' &&
320+
invokedMethod.library.isDartCore;
321+
}
322+
return false;
323+
}
324+
315325
void _resolveArguments(ArgumentList argumentList) {
316-
argumentList.accept(_resolver);
326+
_resolver.visitArgumentList(argumentList,
327+
isIdentical: _isCallToIdentical(argumentList.parent));
317328
}
318329

319330
void _resolveInvocation({

pkg/analyzer/lib/src/generated/resolver.dart

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -788,8 +788,9 @@ class ResolverVisitor extends ScopedVisitor {
788788
}
789789

790790
@override
791-
void visitArgumentList(ArgumentList node) {
791+
void visitArgumentList(ArgumentList node, {bool isIdentical = false}) {
792792
DartType callerType = InferenceContext.getContext(node);
793+
NodeList<Expression> arguments = node.arguments;
793794
if (callerType is FunctionType) {
794795
Map<String, DartType> namedParameterTypes =
795796
callerType.namedParameterTypes;
@@ -798,7 +799,6 @@ class ResolverVisitor extends ScopedVisitor {
798799
int normalCount = normalParameterTypes.length;
799800
int optionalCount = optionalParameterTypes.length;
800801

801-
NodeList<Expression> arguments = node.arguments;
802802
Iterable<Expression> positional =
803803
arguments.takeWhile((l) => l is! NamedExpression);
804804
Iterable<Expression> required = positional.take(normalCount);
@@ -840,7 +840,23 @@ class ResolverVisitor extends ScopedVisitor {
840840
}
841841
}
842842
}
843-
super.visitArgumentList(node);
843+
checkUnreachableNode(node);
844+
int length = arguments.length;
845+
for (var i = 0; i < length; i++) {
846+
if (isIdentical && length > 1 && i == 1) {
847+
var firstArg = arguments[0];
848+
_flowAnalysis?.flow
849+
?.equalityOp_rightBegin(firstArg, firstArg.staticType);
850+
}
851+
arguments[i].accept(this);
852+
}
853+
if (isIdentical && length > 1) {
854+
var secondArg = arguments[1];
855+
_flowAnalysis?.flow
856+
?.equalityOp_end(node.parent, secondArg, secondArg.staticType);
857+
}
858+
node.accept(elementResolver);
859+
node.accept(typeAnalyzer);
844860
}
845861

846862
@override

pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,6 +2004,11 @@ class TypeInferrerImpl implements TypeInferrer {
20042004
new List<DartType>.filled(
20052005
calleeTypeParameters.length, const DynamicType()));
20062006
}
2007+
TreeNode parent = arguments.parent;
2008+
bool isIdentical = arguments.positional.length == 2 &&
2009+
parent is StaticInvocation &&
2010+
parent.target.name.name == 'identical' &&
2011+
parent.target.parent == typeSchemaEnvironment.coreTypes.coreLibrary;
20072012
// TODO(paulberry): if we are doing top level inference and type arguments
20082013
// were omitted, report an error.
20092014
for (int position = 0; position < arguments.positional.length; position++) {
@@ -2044,6 +2049,14 @@ class TypeInferrerImpl implements TypeInferrer {
20442049
: legacyErasure(result.inferredType);
20452050
Expression expression =
20462051
_hoist(result.expression, inferredType, hoistedExpressions);
2052+
if (isIdentical && arguments.positional.length == 2) {
2053+
if (position == 0) {
2054+
flowAnalysis?.equalityOp_rightBegin(expression, inferredType);
2055+
} else {
2056+
flowAnalysis?.equalityOp_end(
2057+
arguments.parent, expression, inferredType);
2058+
}
2059+
}
20472060
arguments.positional[position] = expression..parent = arguments;
20482061
}
20492062
if (inferenceNeeded || typeChecksNeeded) {

0 commit comments

Comments
 (0)