Skip to content

Commit c3053f0

Browse files
MichaelRFairhurstcommit-bot@chromium.org
authored andcommitted
[nnbd_migration] Restrict downcasts; make all type parameters nullable.
This makes trial_migration.dart report two new exceptions: _EdgeBuilder&GeneralizingAstVisitor&_AssignmentChecker._checkAssignment (package:nnbd_migration/src/edge_builder.dart:2169:14) (x30) _EdgeBuilder&GeneralizingAstVisitor&_AssignmentChecker._checkDowncast (package:nnbd_migration/src/edge_builder.dart:2351:14) (x7) 30 & 7 is not great but not terrible. These also do not break path, logging, or charcode. Failing tests show why we cannot correctly cover remaining cases without a larger CL. Change-Id: I6e510db147f60cc8ee047d45e4ce2d95d26cccc4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119533 Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Mike Fairhurst <mfairhurst@google.com>
1 parent 7486512 commit c3053f0

File tree

4 files changed

+156
-29
lines changed

4 files changed

+156
-29
lines changed

pkg/analysis_server/test/domain_edit_dartfix_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ f(Iterable<int> i) {
190190
''');
191191
}
192192

193+
@failingTest
193194
test_dartfix_nonNullable() async {
195+
// Failing because this contains a side-cast from Null to int.
194196
createAnalysisOptionsFile(experiments: ['non-nullable']);
195197
addTestFile('''
196198
int f(int i) => 0;

pkg/nnbd_migration/lib/src/edge_builder.dart

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
16101610
destinationType = destinationExpression.accept(this);
16111611
}
16121612
}
1613+
16131614
if (questionAssignNode != null) {
16141615
_guards.add(destinationType.node);
16151616
}
@@ -2153,6 +2154,21 @@ mixin _AssignmentChecker {
21532154
{@required DecoratedType source,
21542155
@required DecoratedType destination,
21552156
@required bool hard}) {
2157+
var sourceType = source.type;
2158+
var destinationType = destination.type;
2159+
if (!_typeSystem.isSubtypeOf(sourceType, destinationType)) {
2160+
// Not a proper upcast assignment.
2161+
if (_typeSystem.isSubtypeOf(destinationType, sourceType)) {
2162+
// But rather a downcast.
2163+
_checkDowncast(origin,
2164+
source: source, destination: destination, hard: hard);
2165+
return;
2166+
}
2167+
// Neither a proper upcast assignment nor an implicit downcast (some
2168+
// illegal code, or we did something wrong to get here).
2169+
assert(false, 'side cast not supported: $sourceType to $destinationType');
2170+
return;
2171+
}
21562172
_connect(source.node, destination.node, origin, hard: hard);
21572173
_checkAssignment_recursion(origin,
21582174
source: source, destination: destination);
@@ -2165,35 +2181,7 @@ mixin _AssignmentChecker {
21652181
{@required DecoratedType source, @required DecoratedType destination}) {
21662182
var sourceType = source.type;
21672183
var destinationType = destination.type;
2168-
if (!_typeSystem.isSubtypeOf(sourceType, destinationType)) {
2169-
// Not a proper upcast assignment. It is either an implicit downcast or
2170-
// some illegal code. It's handled on a "best effort" basis.
2171-
if (destinationType is TypeParameterType &&
2172-
sourceType is! TypeParameterType) {
2173-
// Assume an assignment to the type parameter's bound.
2174-
_checkAssignment(origin,
2175-
source: source,
2176-
destination:
2177-
_getTypeParameterTypeBound(destination).withNode(_graph.always),
2178-
hard: false);
2179-
return;
2180-
}
2181-
if (sourceType is InterfaceType && destinationType is InterfaceType) {
2182-
if (_typeSystem.isSubtypeOf(destinationType, sourceType)) {
2183-
var rewrittenDestination = _decoratedClassHierarchy.asInstanceOf(
2184-
destination, sourceType.element);
2185-
assert(rewrittenDestination.typeArguments.length ==
2186-
source.typeArguments.length);
2187-
for (int i = 0; i < rewrittenDestination.typeArguments.length; i++) {
2188-
_checkAssignment(origin,
2189-
source: source.typeArguments[i],
2190-
destination: rewrittenDestination.typeArguments[i],
2191-
hard: false);
2192-
}
2193-
}
2194-
}
2195-
return;
2196-
}
2184+
assert(_typeSystem.isSubtypeOf(sourceType, destinationType));
21972185
if (destinationType.isDartAsyncFutureOr) {
21982186
var s1 = destination.typeArguments[0];
21992187
if (sourceType.isDartAsyncFutureOr) {
@@ -2327,6 +2315,63 @@ mixin _AssignmentChecker {
23272315
}
23282316
}
23292317

2318+
void _checkDowncast(EdgeOrigin origin,
2319+
{@required DecoratedType source,
2320+
@required DecoratedType destination,
2321+
@required bool hard}) {
2322+
assert(_typeSystem.isSubtypeOf(destination.type, source.type));
2323+
// Nullability should narrow to maintain subtype relationship.
2324+
_connect(source.node, destination.node, origin, hard: hard);
2325+
if (source.type.isDynamic) {
2326+
assert(destination.typeFormals?.isEmpty ?? true,
2327+
'downcast to something with type parameters not yet supported.');
2328+
assert(destination is! FunctionType,
2329+
'downcast to function type not yet supported.');
2330+
if (destination.type is ParameterizedType) {
2331+
for (final param
2332+
in (destination.type as ParameterizedType).typeParameters) {
2333+
assert(param.type.bound.isDynamic,
2334+
'downcast to type parameters with bounds not supported');
2335+
}
2336+
}
2337+
2338+
for (final arg in destination.typeArguments) {
2339+
// We cannot assume we're downcasting to C<T!>. Downcast to C<T?>.
2340+
_checkDowncast(origin, source: source, destination: arg, hard: false);
2341+
}
2342+
} else if (destination.type is TypeParameterType &&
2343+
source.type is! TypeParameterType) {
2344+
// Assume an assignment to the type parameter's bound.
2345+
_checkAssignment(origin,
2346+
source: source,
2347+
destination:
2348+
_getTypeParameterTypeBound(destination).withNode(_graph.always),
2349+
hard: false);
2350+
} else if (destination.type is InterfaceTypeImpl) {
2351+
assert(source.typeArguments.isEmpty,
2352+
'downcast from interface type with type args not supported.');
2353+
if (destination.type is ParameterizedType) {
2354+
for (final param
2355+
in (destination.type as ParameterizedType).typeParameters) {
2356+
assert(param.type.bound.isDynamic,
2357+
'downcast to type parameters with bounds not supported');
2358+
}
2359+
}
2360+
for (final arg in destination.typeArguments) {
2361+
// We cannot assume we're downcasting to C<T!>. Downcast to C<T?>.
2362+
_checkDowncast(origin,
2363+
source: DecoratedType(_typeProvider.dynamicType, _graph.always),
2364+
destination: arg,
2365+
hard: false);
2366+
}
2367+
} else {
2368+
assert(
2369+
false,
2370+
'downcasting from ${source.type.runtimeType} to '
2371+
'${destination.type.runtimeType} not supported.');
2372+
}
2373+
}
2374+
23302375
void _connect(
23312376
NullabilityNode source, NullabilityNode destination, EdgeOrigin origin,
23322377
{bool hard = false});

pkg/nnbd_migration/test/api_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,82 @@ int f(int i) {
981981
await _checkSingleFileChanges(content, expected);
982982
}
983983

984+
@failingTest
985+
test_downcast_not_widest_type_type_parameters() async {
986+
// Fails because a hard assignment from List<int/*1*/> to List<int/*2*/>
987+
// doesn't create a hard edge from 1 to 2. Perhaps this is correct. In this
988+
// example it seems incorrect.
989+
var content = '''
990+
void f(dynamic a) {
991+
List<int> hardToNonNullNonNull = a;
992+
List<int> hardToNullNonNull = a;
993+
List<int> hardToNonNullNull = a;
994+
List<int/*!*/>/*!*/ nonNullNonNull;
995+
List<int/*?*/>/*!*/ nullNonNull;
996+
List<int/*!*/>/*?*/ nonNullNull;
997+
nonNullNonNull = hardToNonNullNonNull
998+
nonNullNull = hardToNonNullNull
999+
nullNonNull = hardToNullNonNull
1000+
}
1001+
''';
1002+
1003+
// TODO(paulberry): remove the /*!*/, /*?*/ comments on migration.
1004+
var expected = '''
1005+
void f(dynamic a) {
1006+
List<int> hardToNonNullNonNull = a;
1007+
List<int?> hardToNullNonNull = a;
1008+
List<int>? hardToNonNullNull = a;
1009+
List<int/*!*/>/*!*/ nonNullNonNull;
1010+
List<int/*?*/>/*!*/ nullNonNull;
1011+
List<int/*!*/>/*?*/ nonNullNull;
1012+
nonNullNonNull = hardToNonNullNonNull
1013+
nonNullNull = hardToNonNullNull
1014+
nullNonNull = hardToNullNonNull
1015+
}
1016+
''';
1017+
await _checkSingleFileChanges(content, expected);
1018+
}
1019+
1020+
@failingTest
1021+
test_downcast_widest_type_from_related_type_parameters() async {
1022+
var content = '''
1023+
List<int> f(Iterable<int/*?*/> a) => a;
1024+
''';
1025+
1026+
// TODO(paulberry): remove the /*!*/, /*?*/ comments on migration.
1027+
var expected = '''
1028+
List<int?> f(Iterable<int/*?*/> a) => a;
1029+
''';
1030+
await _checkSingleFileChanges(content, expected);
1031+
}
1032+
1033+
test_downcast_widest_type_from_top_type_parameters() async {
1034+
var content = '''
1035+
List<int> f1(dynamic a) => a;
1036+
List<int> f2(Object b) => b;
1037+
''';
1038+
var expected = '''
1039+
List<int?>? f1(dynamic a) => a;
1040+
List<int?> f2(Object b) => b;
1041+
''';
1042+
await _checkSingleFileChanges(content, expected);
1043+
}
1044+
1045+
@failingTest
1046+
test_downcast_widest_type_from_unrelated_type_parameters() async {
1047+
var content = '''
1048+
abstract class C<A, B> implements List<A> {}
1049+
C<int, num> f(List<int> a) => a;
1050+
''';
1051+
1052+
// TODO(paulberry): remove the /*!*/, /*?*/ comments on migration.
1053+
var expected = '''
1054+
abstract class C<A, B> implements List<A> {}
1055+
C<int, num?> f(List<int> a) => a;
1056+
''';
1057+
await _checkSingleFileChanges(content, expected);
1058+
}
1059+
9841060
test_dynamic_method_call() async {
9851061
var content = '''
9861062
class C {

pkg/nnbd_migration/test/edge_builder_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ class AssignmentCheckerTest extends Object
189189
assertNoEdge(t.typeArguments[0].node, anyNode);
190190
}
191191

192+
@failingTest
192193
test_generic_to_generic_downcast() {
193194
var t1 = list(list(object()));
194195
var t2 = myListOfList(object());
@@ -452,6 +453,7 @@ class C<T extends List<int>> {
452453
var tType = decoratedTypeAnnotation('T f');
453454
assertEdge(parameterType.node, tType.node, hard: true);
454455
assertNoEdge(parameterType.node, boundType.node);
456+
// TODO(mfairhurst): Confirm we want this edge.
455457
assertEdge(
456458
parameterType.typeArguments[0].node, boundType.typeArguments[0].node,
457459
hard: false);
@@ -639,7 +641,9 @@ C f(C y, C z) => (y += z);
639641
assertNullCheck(checkExpression('(y += z)'), fReturnEdge);
640642
}
641643

644+
@failingTest
642645
test_assignmentExpression_compound_withSubstitution() async {
646+
// Failing due to a side-cast from incorrectly instantiating the operator.
643647
var code = '''
644648
abstract class C<T> {
645649
C<T> operator+(C<T> x);

0 commit comments

Comments
 (0)