Migration: address a more complex case of instantiate-to-bounds.
Change-Id: I8a7d5e2e4189c3d910077ad4ec88e6c4e384dc25
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105731
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/src/graph_builder.dart b/pkg/nnbd_migration/lib/src/graph_builder.dart
index c7f7b14..bfe82d0 100644
--- a/pkg/nnbd_migration/lib/src/graph_builder.dart
+++ b/pkg/nnbd_migration/lib/src/graph_builder.dart
@@ -499,18 +499,27 @@
DecoratedType visitTypeName(TypeName typeName) {
var typeArguments = typeName.typeArguments?.arguments;
var element = typeName.name.staticElement;
- if (typeArguments != null) {
- for (int i = 0; i < typeArguments.length; i++) {
- DecoratedType bound;
- if (element is TypeParameterizedElement) {
+ if (element is TypeParameterizedElement) {
+ if (typeArguments == null) {
+ var instantiatedType =
+ _variables.decoratedTypeAnnotation(_source, typeName);
+ for (int i = 0; i < instantiatedType.typeArguments.length; i++) {
+ _unionDecoratedTypes(
+ instantiatedType.typeArguments[0],
+ _variables.decoratedElementType(element.typeParameters[i],
+ create: true));
+ }
+ } else {
+ for (int i = 0; i < typeArguments.length; i++) {
+ DecoratedType bound;
bound = _variables.decoratedElementType(element.typeParameters[i],
create: true);
- } else {
- throw new UnimplementedError('TODO(paulberry)');
+ _checkAssignment(
+ bound,
+ _variables.decoratedTypeAnnotation(_source, typeArguments[i]),
+ null,
+ hard: true);
}
- _checkAssignment(bound,
- _variables.decoratedTypeAnnotation(_source, typeArguments[i]), null,
- hard: true);
}
}
return DecoratedType(typeName.type, _graph.never);
@@ -669,6 +678,20 @@
}
return false;
}
+
+ void _unionDecoratedTypes(DecoratedType x, DecoratedType y) {
+ _graph.union(x.node, y.node);
+ if (x.typeArguments.isNotEmpty ||
+ y.typeArguments.isNotEmpty ||
+ x.returnType != null ||
+ y.returnType != null ||
+ x.positionalParameters.isNotEmpty ||
+ y.positionalParameters.isNotEmpty ||
+ x.namedParameters.isNotEmpty ||
+ y.namedParameters.isNotEmpty) {
+ throw UnimplementedError('TODO(paulberry): _unionDecoratedTypes($x, $y)');
+ }
+ }
}
/// Information about a binary expression whose boolean value could possibly
diff --git a/pkg/nnbd_migration/lib/src/node_builder.dart b/pkg/nnbd_migration/lib/src/node_builder.dart
index 51548fd..89ff432 100644
--- a/pkg/nnbd_migration/lib/src/node_builder.dart
+++ b/pkg/nnbd_migration/lib/src/node_builder.dart
@@ -237,6 +237,10 @@
DecoratedType _decorateImplicitTypeArgument(DartType type) {
if (type.isDynamic) {
return DecoratedType(type, _graph.always);
+ } else if (type is InterfaceType) {
+ return DecoratedType(type, NullabilityNode.forInferredType(),
+ typeArguments:
+ type.typeArguments.map(_decorateImplicitTypeArgument).toList());
}
throw UnimplementedError('TODO(paulberry): ${type.runtimeType}');
}
diff --git a/pkg/nnbd_migration/lib/src/nullability_node.dart b/pkg/nnbd_migration/lib/src/nullability_node.dart
index 2935a26..b8fd097 100644
--- a/pkg/nnbd_migration/lib/src/nullability_node.dart
+++ b/pkg/nnbd_migration/lib/src/nullability_node.dart
@@ -273,6 +273,11 @@
return node;
}
+ /// Creates a [NullabilityNode] representing the nullability of a variable
+ /// whose type is determined by type inference.
+ factory NullabilityNode.forInferredType() =>
+ _NullabilityNodeSimple('inferred');
+
/// Creates a [NullabilityNode] representing the nullability of an
/// expression which is nullable iff both [a] and [b] are nullable.
///
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 07bb061..c4e8a9a 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -657,6 +657,33 @@
await _checkSingleFileChanges(content, expected);
}
+ test_genericType_noTypeArguments_use_bound() async {
+ var content = '''
+abstract class C<T extends Object> { // (1)
+ void put(T t);
+ T get();
+}
+Object f(C c) => c.get(); // (2)
+void g(C<int> c) { // (3)
+ c.put(null); // (4)
+}
+''';
+ // (4) forces `...C<int?>...` at (3), which means (1) must be
+ // `...extends Object?`. Therefore (2) is equivalent to
+ // `...f(C<Object?> c)...`, so the return type of `f` is `Object?`.
+ var expected = '''
+abstract class C<T extends Object?> { // (1)
+ void put(T t);
+ T get();
+}
+Object? f(C c) => c.get(); // (2)
+void g(C<int?> c) { // (3)
+ c.put(null); // (4)
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_getter_topLevel() async {
var content = '''
int get g => 0;
diff --git a/pkg/nnbd_migration/test/migration_visitor_test.dart b/pkg/nnbd_migration/test/migration_visitor_test.dart
index 0b96814..f897472 100644
--- a/pkg/nnbd_migration/test/migration_visitor_test.dart
+++ b/pkg/nnbd_migration/test/migration_visitor_test.dart
@@ -1162,6 +1162,16 @@
assertNoUpstreamNullability(decoratedTypeAnnotation('Type').node);
}
+ test_typeName_union_with_bound() async {
+ await analyze('''
+class C<T extends Object> {}
+void f(C c) {}
+''');
+ var cType = decoratedTypeAnnotation('C c');
+ var cBound = decoratedTypeAnnotation('Object');
+ assertUnion(cType.typeArguments[0].node, cBound.node);
+ }
+
test_variableDeclaration() async {
await analyze('''
void f(int i) {
@@ -1352,6 +1362,40 @@
expect(decoratedArgType.node, same(always));
}
+ test_interfaceType_generic_instantiate_to_generic_type() async {
+ await analyze('''
+class C<T> {}
+class D<T extends C<int>> {}
+void f(D x) {}
+''');
+ var decoratedListType = decoratedTypeAnnotation('D x');
+ expect(decoratedFunctionType('f').positionalParameters[0],
+ same(decoratedListType));
+ expect(decoratedListType.node, TypeMatcher<NullabilityNodeMutable>());
+ expect(decoratedListType.typeArguments, hasLength(1));
+ var decoratedArgType = decoratedListType.typeArguments[0];
+ expect(decoratedArgType.node, TypeMatcher<NullabilityNodeMutable>());
+ expect(decoratedArgType.typeArguments, hasLength(1));
+ var decoratedArgArgType = decoratedArgType.typeArguments[0];
+ expect(decoratedArgArgType.node, TypeMatcher<NullabilityNodeMutable>());
+ expect(decoratedArgArgType.typeArguments, isEmpty);
+ }
+
+ test_interfaceType_generic_instantiate_to_object() async {
+ await analyze('''
+class C<T extends Object> {}
+void f(C x) {}
+''');
+ var decoratedListType = decoratedTypeAnnotation('C x');
+ expect(decoratedFunctionType('f').positionalParameters[0],
+ same(decoratedListType));
+ expect(decoratedListType.node, TypeMatcher<NullabilityNodeMutable>());
+ expect(decoratedListType.typeArguments, hasLength(1));
+ var decoratedArgType = decoratedListType.typeArguments[0];
+ expect(decoratedArgType.node, TypeMatcher<NullabilityNodeMutable>());
+ expect(decoratedArgType.typeArguments, isEmpty);
+ }
+
test_interfaceType_typeParameter() async {
await analyze('''
void f(List<int> x) {}