Skip to content

Commit c8587ce

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Flow analysis: don't use == to compare types.
Includes an integration test illustrating where this would have caused incorrect behavior (due to the fact that the analyzer's `DartType.==` doesn't yet distinguish nullabilities - see #37587), as well as unit tests that will ensure we don't regress even after #37587 is fixed. Change-Id: Ief92cf5a052bc2b96e4e74f69f2268b0565d920f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/109820 Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent 7b6c667 commit c8587ce

File tree

5 files changed

+68
-10
lines changed

5 files changed

+68
-10
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,9 @@ abstract class TypeOperations<Variable, Type> {
655655
/// Return `true` if the [variable] is a local variable, not a parameter.
656656
bool isLocalVariable(Variable variable);
657657

658+
/// Returns `true` if [type1] and [type2] are the same type.
659+
bool isSameType(Type type1, Type type2);
660+
658661
/// Return `true` if the [leftType] is a subtype of the [rightType].
659662
bool isSubtypeOf(Type leftType, Type rightType);
660663

@@ -733,7 +736,7 @@ class _State<Variable, Type> {
733736
previousType ??= typeOperations.variableType(variable);
734737

735738
if (typeOperations.isSubtypeOf(type, previousType) &&
736-
type != previousType) {
739+
!typeOperations.isSameType(type, previousType)) {
737740
var newPromoted = <Variable, Type>{}..addAll(promoted);
738741
newPromoted[variable] = type;
739742
return _State<Variable, Type>(

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,16 @@ class _TypeSystemTypeOperations
463463
return element is LocalVariableElement;
464464
}
465465

466+
@override
467+
bool isSameType(covariant TypeImpl type1, covariant TypeImpl type2) {
468+
if (type1.nullabilitySuffix != type2.nullabilitySuffix) {
469+
// TODO(paulberry): after DartType.operator== has been updated to compare
470+
// nullabilities, this if test can be dropped. See dartbug.com/37587.
471+
return false;
472+
}
473+
return type1 == type2;
474+
}
475+
466476
@override
467477
bool isSubtypeOf(DartType leftType, DartType rightType) {
468478
return typeSystem.isSubtypeOf(leftType, rightType);
@@ -471,15 +481,7 @@ class _TypeSystemTypeOperations
471481
@override
472482
DartType tryPromoteToNonNull(covariant TypeImpl type) {
473483
TypeImpl promotedType = typeSystem.promoteToNonNull(type);
474-
if (promotedType.nullabilitySuffix != type.nullabilitySuffix) {
475-
// TODO(paulberry): after DartType.operator== has been updated to compare
476-
// nullabilities, this if test can be dropped. See dartbug.com/37587.
477-
return promotedType;
478-
}
479-
if (promotedType != type) {
480-
return promotedType;
481-
}
482-
return null;
484+
return isSameType(type, promotedType) ? null : promotedType;
483485
}
484486

485487
@override

pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,18 @@ void f(int? x) {
193193
''');
194194
}
195195

196+
test_is_promotes_nullability() async {
197+
await trackCode(r'''
198+
void f(int? x) {
199+
if (x is int) {
200+
/*nonNullable*/ x;
201+
} else {
202+
x;
203+
}
204+
}
205+
''');
206+
}
207+
196208
test_method_if_then_else() async {
197209
await trackCode(r'''
198210
class C {

pkg/analyzer/test/src/dart/resolution/flow_analysis_unit_test.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,37 @@ main() {
4747
expect(flow.promotedType(x).type, 'int');
4848
flow.verifyStackEmpty();
4949
});
50+
51+
void _checkIs(String declaredType, String tryPromoteType,
52+
String expectedPromotedType) {
53+
var flow = _Harness().flow;
54+
var x = _Var('x', _Type(declaredType));
55+
flow.add(x, assigned: true);
56+
var expr = _Expression();
57+
flow.isExpression_end(expr, x, false, _Type(tryPromoteType));
58+
flow.ifStatement_thenBegin(expr);
59+
if (expectedPromotedType == null) {
60+
expect(flow.promotedType(x), isNull);
61+
} else {
62+
expect(flow.promotedType(x).type, 'int');
63+
}
64+
flow.ifStatement_elseBegin();
65+
expect(flow.promotedType(x), isNull);
66+
flow.ifStatement_end(true);
67+
flow.verifyStackEmpty();
68+
}
69+
70+
test('isExpression_end promotes to a subtype', () {
71+
_checkIs('int?', 'int', 'int');
72+
});
73+
74+
test('isExpression_end does not promote to a supertype', () {
75+
_checkIs('int', 'int?', null);
76+
});
77+
78+
test('isExpression_end does not promote to an unrelated type', () {
79+
_checkIs('int', 'String', null);
80+
});
5081
});
5182

5283
group('join', () {
@@ -146,6 +177,11 @@ class _Harness
146177
throw UnimplementedError('TODO(paulberry)');
147178
}
148179

180+
@override
181+
bool isSameType(_Type type1, _Type type2) {
182+
return type1.type == type2.type;
183+
}
184+
149185
@override
150186
bool isSubtypeOf(_Type leftType, _Type rightType) {
151187
const Map<String, bool> _subtypes = const {

pkg/nnbd_migration/lib/src/decorated_type_operations.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class DecoratedTypeOperations
2121
return element is LocalVariableElement;
2222
}
2323

24+
@override
25+
bool isSameType(DecoratedType type1, DecoratedType type2) {
26+
throw new UnimplementedError('TODO(paulberry)');
27+
}
28+
2429
@override
2530
bool isSubtypeOf(DecoratedType leftType, DecoratedType rightType) {
2631
return _typeSystem.isSubtypeOf(leftType.type, rightType.type);

0 commit comments

Comments
 (0)