Add migration support for property accesses.
Also made some minor improvements to the handling of method calls.
Change-Id: I8728a7901cfe61f88563b71e290e9d5b303afda1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104486
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/decorated_type.dart b/pkg/analysis_server/lib/src/nullability/decorated_type.dart
index 8a4d41a..0dbc5ac 100644
--- a/pkg/analysis_server/lib/src/nullability/decorated_type.dart
+++ b/pkg/analysis_server/lib/src/nullability/decorated_type.dart
@@ -72,6 +72,8 @@
DecoratedType decoratedType;
if (element is MethodElement) {
decoratedType = decorate(element.type);
+ } else if (element is PropertyAccessorElement) {
+ decoratedType = decorate(element.type);
} else {
throw element.runtimeType; // TODO(paulberry)
}
diff --git a/pkg/analysis_server/lib/src/nullability/graph_builder.dart b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
index 10d34a6..c2f4bfa 100644
--- a/pkg/analysis_server/lib/src/nullability/graph_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
@@ -289,7 +289,7 @@
@override
DecoratedType visitMethodDeclaration(MethodDeclaration node) {
- node.parameters.accept(this);
+ node.parameters?.accept(this);
assert(_currentFunctionType == null);
_currentFunctionType =
_variables.decoratedElementType(node.declaredElement);
@@ -306,12 +306,16 @@
DecoratedType visitMethodInvocation(MethodInvocation node) {
DecoratedType targetType;
if (node.target != null) {
- assert(node.operator.type == TokenType.PERIOD);
+ if (node.operator.type != TokenType.PERIOD) {
+ throw new UnimplementedError('TODO(paulberry)');
+ }
_checkNonObjectMember(node.methodName.name); // TODO(paulberry)
targetType = _handleAssignment(_notNullType, node.target);
}
var callee = node.methodName.staticElement;
- assert(callee != null); // TODO(paulberry)
+ if (callee == null) {
+ throw new UnimplementedError('TODO(paulberry)');
+ }
var calleeType = getOrComputeElementType(callee, targetType: targetType);
// TODO(paulberry): substitute if necessary
var arguments = node.argumentList.arguments;
@@ -361,6 +365,20 @@
}
@override
+ DecoratedType visitPrefixedIdentifier(PrefixedIdentifier node) {
+ if (node.prefix.staticElement is ImportElement) {
+ throw new UnimplementedError('TODO(paulberry)');
+ } else {
+ return _handlePropertyAccess(node.prefix, node.period, node.identifier);
+ }
+ }
+
+ @override
+ DecoratedType visitPropertyAccess(PropertyAccess node) {
+ return _handlePropertyAccess(node.target, node.operator, node.propertyName);
+ }
+
+ @override
DecoratedType visitReturnStatement(ReturnStatement node) {
if (node.expression == null) {
_checkAssignment(_currentFunctionType.returnType, _nullType, null);
@@ -487,6 +505,22 @@
return sourceType;
}
+ DecoratedType _handlePropertyAccess(
+ Expression target, Token operator, SimpleIdentifier propertyName) {
+ if (operator.type != TokenType.PERIOD) {
+ throw new UnimplementedError('TODO(paulberry)');
+ }
+ _checkNonObjectMember(propertyName.name); // TODO(paulberry)
+ var targetType = _handleAssignment(_notNullType, target);
+ var callee = propertyName.staticElement;
+ if (callee == null) {
+ throw new UnimplementedError('TODO(paulberry)');
+ }
+ var calleeType = getOrComputeElementType(callee, targetType: targetType);
+ // TODO(paulberry): substitute if necessary
+ return calleeType.returnType;
+ }
+
/// Double checks that [type] is sufficiently simple for this naive prototype
/// implementation.
///
diff --git a/pkg/analysis_server/lib/src/nullability/node_builder.dart b/pkg/analysis_server/lib/src/nullability/node_builder.dart
index 39ad145..b85765b 100644
--- a/pkg/analysis_server/lib/src/nullability/node_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/node_builder.dart
@@ -182,7 +182,7 @@
namedParameters: {});
_currentFunctionType = functionType;
try {
- parameters.accept(this);
+ parameters?.accept(this);
} finally {
_currentFunctionType = previousFunctionType;
}
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 055677ac..357b500 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -621,6 +621,18 @@
contextNode: nullable_i);
}
+ test_methodInvocation_return_type() async {
+ await analyze('''
+class C {
+ bool m() => true;
+}
+bool f(C c) => c.m();
+''');
+ assertEdge(decoratedTypeAnnotation('bool m').node,
+ decoratedTypeAnnotation('bool f').node,
+ hard: false);
+ }
+
test_methodInvocation_target_check() async {
await analyze('''
class C {
@@ -665,6 +677,71 @@
contextNode: decoratedTypeAnnotation('int').node);
}
+ test_prefixedIdentifier_return_type() async {
+ await analyze('''
+class C {
+ bool get b => true;
+}
+bool f(C c) => c.b;
+''');
+ assertEdge(decoratedTypeAnnotation('bool get').node,
+ decoratedTypeAnnotation('bool f').node,
+ hard: false);
+ }
+
+ test_prefixedIdentifier_target_check() async {
+ await analyze('''
+class C {
+ int get x => 1;
+}
+void test(C c) {
+ c.x;
+}
+''');
+
+ assertNullCheck(
+ checkExpression('c.x'), decoratedTypeAnnotation('C c').node);
+ }
+
+ test_prefixedIdentifier_target_demonstrates_non_null_intent() async {
+ await analyze('''
+class C {
+ int get x => 1;
+}
+void test(C c) {
+ c.x;
+}
+''');
+
+ assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true);
+ }
+
+ test_propertyAccess_return_type() async {
+ await analyze('''
+class C {
+ bool get b => true;
+}
+bool f(C c) => (c).b;
+''');
+ assertEdge(decoratedTypeAnnotation('bool get').node,
+ decoratedTypeAnnotation('bool f').node,
+ hard: false);
+ }
+
+ test_propertyAccess_target_check() async {
+ await analyze('''
+class C {
+ int get x => 1;
+}
+void test(C c) {
+ (c).x;
+}
+''');
+
+ assertNullCheck(
+ checkExpression('c).x'), decoratedTypeAnnotation('C c').node);
+ }
+
test_return_implicit_null() async {
verifyNoTestUnitErrors = false;
await analyze('''
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 e230e2a..f6ba883 100644
--- a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
+++ b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
@@ -630,7 +630,7 @@
await _checkSingleFileChanges(content, expected);
}
- test_unconditional_dereference_implies_non_null_intent() async {
+ test_unconditional_method_call_implies_non_null_intent() async {
var content = '''
void f(int i) {
i.abs();
@@ -682,6 +682,32 @@
await _checkSingleFileChanges(content, expected);
}
+ test_unconditional_property_access_implies_non_null_intent() async {
+ var content = '''
+void f(int i) {
+ i.isEven;
+}
+void g(bool b, int i) {
+ if (b) f(i);
+}
+main() {
+ g(false, null);
+}
+''';
+ var expected = '''
+void f(int i) {
+ i.isEven;
+}
+void g(bool b, int? i) {
+ if (b) f(i!);
+}
+main() {
+ g(false, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_unconditional_usage_propagates_non_null_intent() async {
var content = '''
void f(int i) {