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