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) {