Migration: support assignments to fields and setters.

Change-Id: I4dbf43878f5cbf0a16a041e0975880c3d844fa13
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105041
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/graph_builder.dart b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
index ef7aa27..f467a40 100644
--- a/pkg/analysis_server/lib/src/nullability/graph_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
@@ -114,13 +114,17 @@
         baseElement.isSynthetic &&
         !baseElement.variable.isSynthetic) {
       var variable = baseElement.variable;
+      var decoratedElementType =
+          _variables.decoratedElementType(variable, create: true);
       if (baseElement.isGetter) {
         decoratedBaseType = DecoratedType(
             baseElement.type, NullabilityNode.never,
-            returnType:
-                _variables.decoratedElementType(variable, create: true));
+            returnType: decoratedElementType);
       } else {
-        throw UnimplementedError('TODO(paulberry)');
+        assert(baseElement.isSetter);
+        decoratedBaseType = DecoratedType(
+            baseElement.type, NullabilityNode.never,
+            positionalParameters: [decoratedElementType]);
       }
     } else {
       decoratedBaseType =
@@ -575,7 +579,11 @@
     }
     var calleeType = getOrComputeElementType(callee, targetType: targetType);
     // TODO(paulberry): substitute if necessary
-    return calleeType.returnType;
+    if (propertyName.inSetterContext()) {
+      return calleeType.positionalParameters[0];
+    } else {
+      return calleeType.returnType;
+    }
   }
 
   /// Double checks that [type] is sufficiently simple for this naive prototype
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 b325134..daa4bed 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -108,6 +108,33 @@
     assertEdge(decoratedTypeAnnotation('int i').node, never, hard: true);
   }
 
+  test_assignmentExpression_field() async {
+    await analyze('''
+class C {
+  int x = 0;
+}
+void f(C c, int i) {
+  c.x = i;
+}
+''');
+    assertEdge(decoratedTypeAnnotation('int i').node,
+        decoratedTypeAnnotation('int x').node,
+        hard: true);
+  }
+
+  test_assignmentExpression_field_target_check() async {
+    await analyze('''
+class C {
+  int x = 0;
+}
+void f(C c, int i) {
+  c.x = i;
+}
+''');
+    assertNullCheck(
+        checkExpression('c.x'), decoratedTypeAnnotation('C c').node);
+  }
+
   test_assignmentExpression_indexExpression_index() async {
     await analyze('''
 class C {
@@ -183,6 +210,33 @@
         hard: false);
   }
 
+  test_assignmentExpression_setter() async {
+    await analyze('''
+class C {
+  void set s(int value) {}
+}
+void f(C c, int i) {
+  c.s = i;
+}
+''');
+    assertEdge(decoratedTypeAnnotation('int i').node,
+        decoratedTypeAnnotation('int value').node,
+        hard: true);
+  }
+
+  test_assignmentExpression_setter_target_check() async {
+    await analyze('''
+class C {
+  void set s(int value) {}
+}
+void f(C c, int i) {
+  c.s = i;
+}
+''');
+    assertNullCheck(
+        checkExpression('c.s'), decoratedTypeAnnotation('C c').node);
+  }
+
   test_binaryExpression_add_left_check() async {
     await analyze('''
 int f(int i, int j) => i + j;
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 07caad0..99c27ce37 100644
--- a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
+++ b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
@@ -224,7 +224,27 @@
     await _checkSingleFileChanges(content, expected);
   }
 
-  test_data_flow_assignment() async {
+  test_data_flow_assignment_field() async {
+    var content = '''
+class C {
+  int x = 0;
+}
+void f(C c) {
+  c.x = null;
+}
+''';
+    var expected = '''
+class C {
+  int? x = 0;
+}
+void f(C c) {
+  c.x = null;
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  test_data_flow_assignment_local() async {
     var content = '''
 void main() {
   int i = 0;
@@ -240,6 +260,26 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  test_data_flow_assignment_setter() async {
+    var content = '''
+class C {
+  void set s(int value) {}
+}
+void f(C c) {
+  c.s = null;
+}
+''';
+    var expected = '''
+class C {
+  void set s(int? value) {}
+}
+void f(C c) {
+  c.s = null;
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   test_data_flow_field_read() async {
     var content = '''
 class C {