Infer when the bound of a type parameter needs to be nullable.

Change-Id: I6c500318a66e05a0b3d10c85f07660dfe37339ea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103000
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
index e209eea..20532c1 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
@@ -415,6 +415,23 @@
 
   @override
   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) {
+          bound = _variables.decoratedElementType(element.typeParameters[i],
+              create: true);
+        } else {
+          throw new UnimplementedError('TODO(paulberry)');
+        }
+        _checkAssignment(
+            bound,
+            _variables.decoratedTypeAnnotation(_source, typeArguments[i]),
+            null);
+      }
+    }
     return DecoratedType(typeName.type, NullabilityNode.never, _graph);
   }
 
diff --git a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
index f30cd7d..fbabd5a 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
@@ -12,6 +12,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer/src/generated/resolver.dart';
 import 'package:analyzer/src/generated/source.dart';
 
 /// Visitor that gathers constraint variables for nullability migration from
@@ -42,8 +43,10 @@
 
   final NullabilityGraph _graph;
 
+  final TypeProvider _typeProvider;
+
   ConstraintVariableGatherer(this._variables, this._source, this._permissive,
-      this.assumptions, this._graph);
+      this.assumptions, this._graph, this._typeProvider);
 
   /// Creates and stores a [DecoratedType] object corresponding to the given
   /// [type] AST, and returns it.
@@ -158,6 +161,18 @@
   @override
   DecoratedType visitTypeName(TypeName node) => visitTypeAnnotation(node);
 
+  @override
+  DecoratedType visitTypeParameter(TypeParameter node) {
+    var element = node.declaredElement;
+    var decoratedBound = node.bound?.accept(this) ??
+        DecoratedType(
+            element.bound ?? _typeProvider.objectType,
+            NullabilityNode.forInferredDynamicType(_graph, node.offset),
+            _graph);
+    _variables.recordDecoratedElementType(element, decoratedBound);
+    return null;
+  }
+
   /// Common handling of function and method declarations.
   void _handleExecutableDeclaration(
       ExecutableElement declaredElement,
@@ -219,6 +234,10 @@
   /// element, one is synthesized using [DecoratedType.forElement].
   DecoratedType decoratedElementType(Element element, {bool create: false});
 
+  /// Gets the [DecoratedType] associated with the given [typeAnnotation].
+  DecoratedType decoratedTypeAnnotation(
+      Source source, TypeAnnotation typeAnnotation);
+
   /// Records conditional discard information for the given AST node (which is
   /// an `if` statement or a conditional (`?:`) expression).
   void recordConditionalDiscard(
diff --git a/pkg/analysis_server/lib/src/nullability/provisional_api.dart b/pkg/analysis_server/lib/src/nullability/provisional_api.dart
index 99080c9..36b741b 100644
--- a/pkg/analysis_server/lib/src/nullability/provisional_api.dart
+++ b/pkg/analysis_server/lib/src/nullability/provisional_api.dart
@@ -94,7 +94,7 @@
   }
 
   void prepareInput(ResolvedUnitResult result) {
-    _analyzerMigration.prepareInput(result.unit);
+    _analyzerMigration.prepareInput(result.unit, result.typeProvider);
   }
 
   void processInput(ResolvedUnitResult result) {
diff --git a/pkg/analysis_server/lib/src/nullability/transitional_api.dart b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
index d05c141..f49c106 100644
--- a/pkg/analysis_server/lib/src/nullability/transitional_api.dart
+++ b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
@@ -158,9 +158,14 @@
     return _variables.getPotentialModifications();
   }
 
-  void prepareInput(CompilationUnit unit) {
-    unit.accept(ConstraintVariableGatherer(_variables,
-        unit.declaredElement.source, _permissive, assumptions, _graph));
+  void prepareInput(CompilationUnit unit, TypeProvider typeProvider) {
+    unit.accept(ConstraintVariableGatherer(
+        _variables,
+        unit.declaredElement.source,
+        _permissive,
+        assumptions,
+        _graph,
+        typeProvider));
   }
 
   void processInput(CompilationUnit unit, TypeProvider typeProvider) {
@@ -257,6 +262,9 @@
 class Variables implements VariableRecorder, VariableRepository {
   final _decoratedElementTypes = <Element, DecoratedType>{};
 
+  final _decoratedTypeAnnotations =
+      <Source, Map<int, DecoratedTypeAnnotation>>{};
+
   final _potentialModifications = <Source, List<PotentialModification>>{};
 
   final NullabilityGraph _graph;
@@ -269,6 +277,13 @@
           ? DecoratedType.forElement(element, _graph)
           : throw StateError('No element found');
 
+  @override
+  DecoratedType decoratedTypeAnnotation(
+      Source source, TypeAnnotation typeAnnotation) {
+    return _decoratedTypeAnnotations[source]
+        [_uniqueOffsetForTypeAnnotation(typeAnnotation)];
+  }
+
   Map<Source, List<PotentialModification>> getPotentialModifications() =>
       _potentialModifications;
 
@@ -288,6 +303,8 @@
   void recordDecoratedTypeAnnotation(
       Source source, TypeAnnotation node, DecoratedTypeAnnotation type) {
     _addPotentialModification(source, type);
+    (_decoratedTypeAnnotations[source] ??=
+        {})[_uniqueOffsetForTypeAnnotation(node)] = type;
   }
 
   @override
@@ -354,6 +371,11 @@
       Source source, PotentialModification potentialModification) {
     (_potentialModifications[source] ??= []).add(potentialModification);
   }
+
+  int _uniqueOffsetForTypeAnnotation(TypeAnnotation typeAnnotation) =>
+      typeAnnotation is GenericFunctionType
+          ? typeAnnotation.functionKeyword.offset
+          : typeAnnotation.offset;
 }
 
 /// Helper object used by [ConditionalModification] to keep track of AST nodes
diff --git a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
index 22acec6..b143f73 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -767,6 +767,15 @@
     assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
+  test_type_argument_explicit_bound() async {
+    await analyze('''
+class C<T extends Object> {}
+void f(C<int> c) {}
+''');
+    assertConnection(decoratedTypeAnnotation('int>').node,
+        decoratedTypeAnnotation('Object>').node);
+  }
+
   test_typeName() async {
     await analyze('''
 Type f() {
@@ -799,6 +808,9 @@
       _variables.decoratedElementType(
           findNode.functionDeclaration(search).declaredElement);
 
+  DecoratedType decoratedTypeParameterBound(String search) => _variables
+      .decoratedElementType(findNode.typeParameter(search).declaredElement);
+
   test_interfaceType_typeParameter() async {
     await analyze('''
 void f(List<int> x) {}
@@ -908,6 +920,29 @@
     expect(decoratedType.node, isNotNull);
     expect(decoratedType.node, isNot(NullabilityNode.never));
   }
+
+  test_type_parameter_explicit_bound() async {
+    await analyze('''
+class C<T extends Object> {}
+''');
+    var bound = decoratedTypeParameterBound('T');
+    expect(decoratedTypeAnnotation('Object'), same(bound));
+    expect(bound.node, isNot(NullabilityNode.always));
+    expect(bound.type, typeProvider.objectType);
+  }
+
+  test_type_parameter_implicit_bound() async {
+    // The implicit bound of `T` is automatically `Object?`.  TODO(paulberry):
+    // consider making it possible for type inference to infer an explicit bound
+    // of `Object`.
+    await analyze('''
+class C<T> {}
+''');
+    var bound = decoratedTypeParameterBound('T');
+    expect(graph.getUnconditionalUpstreamNodes(bound.node),
+        contains(NullabilityNode.always));
+    expect(bound.type, same(typeProvider.objectType));
+  }
 }
 
 class MigrationVisitorTestBase extends AbstractSingleUnitTest {
@@ -928,7 +963,7 @@
           const NullabilityMigrationAssumptions()}) async {
     await resolveTestUnit(code);
     testUnit.accept(ConstraintVariableGatherer(
-        _variables, testSource, false, assumptions, graph));
+        _variables, testSource, false, assumptions, graph, typeProvider));
     findNode = FindNode(code, testUnit);
     return testUnit;
   }
@@ -936,7 +971,8 @@
   /// Gets the [DecoratedType] associated with the type annotation whose text
   /// is [text].
   DecoratedType decoratedTypeAnnotation(String text) {
-    return _variables.decoratedTypeAnnotation(findNode.typeAnnotation(text));
+    return _variables.decoratedTypeAnnotation(
+        testSource, findNode.typeAnnotation(text));
   }
 
   NullabilityNode possiblyOptionalParameter(String text) {
@@ -957,8 +993,6 @@
 
   final _decoratedExpressionTypes = <Expression, DecoratedType>{};
 
-  final _decoratedTypeAnnotations = <TypeAnnotation, DecoratedType>{};
-
   final _expressionChecks = <Expression, ExpressionChecks>{};
 
   final _possiblyOptional = <DefaultFormalParameter, NullabilityNode>{};
@@ -977,10 +1011,6 @@
   DecoratedType decoratedExpressionType(Expression expression) =>
       _decoratedExpressionTypes[_normalizeExpression(expression)];
 
-  /// Gets the [DecoratedType] associated with the given [typeAnnotation].
-  DecoratedType decoratedTypeAnnotation(TypeAnnotation typeAnnotation) =>
-      _decoratedTypeAnnotations[typeAnnotation];
-
   /// Gets the [NullabilityNode] associated with the possibility that
   /// [parameter] may be optional.
   NullabilityNode possiblyOptionalParameter(DefaultFormalParameter parameter) =>
@@ -998,12 +1028,6 @@
     _decoratedExpressionTypes[_normalizeExpression(node)] = type;
   }
 
-  void recordDecoratedTypeAnnotation(
-      Source source, TypeAnnotation node, DecoratedType type) {
-    super.recordDecoratedTypeAnnotation(source, node, type);
-    _decoratedTypeAnnotations[node] = type;
-  }
-
   @override
   void recordExpressionChecks(
       Source source, Expression expression, ExpressionChecks checks) {
diff --git a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
index 001610c..8a4bb19 100644
--- a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
+++ b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
@@ -762,6 +762,34 @@
         {path1: file1, path2: file2}, {path1: expected1, path2: expected2});
   }
 
+  test_type_argument_flows_to_bound() async {
+    // The inference of C<int?> forces class C to be declared as
+    // C<T extends Object?>.
+    var content = '''
+class C<T extends Object> {
+  void m(T t);
+}
+class D<T extends Object> {
+  void m(T t);
+}
+f(C<int> c, D<int> d) {
+  c.m(null);
+}
+''';
+    var expected = '''
+class C<T extends Object?> {
+  void m(T t);
+}
+class D<T extends Object> {
+  void m(T t);
+}
+f(C<int?> c, D<int> d) {
+  c.m(null);
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   test_unconditional_assert_statement_implies_non_null_intent() async {
     var content = '''
 void f(int i) {