Migration: add isCompound parameter to FixBuilder.visitAssignmentTarget.

When visiting an assignment target that is an index expression
(`x[y]`) we'll need to know whether we are doing a compound assignment
or not, because this will influence whether the type context for the
index expression (`y`) should come from `operator[]` or `operator[]=`.

So before adding index expression support to FixBuilder, let's add an
`isCompound` boolean to indicate whether the assignment context is a
compound assignment or not.

Change-Id: I26a41544e10ef9c9ba042c1d7862563bd9d18b68
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121407
Reviewed-by: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index f3abc57..6322c06 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -126,10 +126,12 @@
 
   @override
   DartType visitAssignmentExpression(AssignmentExpression node) {
-    var targetInfo = visitAssignmentTarget(node.leftHandSide);
-    if (node.operator.type == TokenType.EQ) {
+    var operatorType = node.operator.type;
+    var targetInfo =
+        visitAssignmentTarget(node.leftHandSide, operatorType != TokenType.EQ);
+    if (operatorType == TokenType.EQ) {
       return visitSubexpression(node.rightHandSide, targetInfo.writeType);
-    } else if (node.operator.type == TokenType.QUESTION_QUESTION_EQ) {
+    } else if (operatorType == TokenType.QUESTION_QUESTION_EQ) {
       // TODO(paulberry): if targetInfo.readType is non-nullable, then the
       // assignment is dead code.
       // See https://github.com/dart-lang/sdk/issues/38678
@@ -166,14 +168,17 @@
 
   /// Recursively visits an assignment target, returning information about the
   /// target's read and write types.
-  AssignmentTargetInfo visitAssignmentTarget(Expression node) {
+  ///
+  /// If [isCompound] is true, the target is being both read from and written
+  /// to.  If it is false, then only the write type is needed.
+  AssignmentTargetInfo visitAssignmentTarget(Expression node, bool isCompound) {
     if (node is SimpleIdentifier) {
       var writeType = _computeMigratedType(node.staticElement);
       var auxiliaryElements = node.auxiliaryElements;
       var readType = auxiliaryElements == null
           ? writeType
           : _computeMigratedType(auxiliaryElements.staticElement);
-      return AssignmentTargetInfo(readType, writeType);
+      return AssignmentTargetInfo(isCompound ? readType : null, writeType);
     } else {
       throw UnimplementedError('TODO(paulberry)');
     }
@@ -341,7 +346,7 @@
           'TODO(paulberry): re-migration of already migrated code not '
           'supported yet');
     } else {
-      var targetInfo = visitAssignmentTarget(node.operand);
+      var targetInfo = visitAssignmentTarget(node.operand, true);
       _handleIncrementOrDecrement(node.staticElement, targetInfo, node);
       return targetInfo.readType;
     }
@@ -371,7 +376,7 @@
       case TokenType.PLUS_PLUS:
       case TokenType.MINUS_MINUS:
         return _handleIncrementOrDecrement(
-            node.staticElement, visitAssignmentTarget(operand), node);
+            node.staticElement, visitAssignmentTarget(operand, true), node);
       default:
         throw StateError('Unexpected prefix operator: ${node.operator}');
     }
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index f25ba0b..143c4a7 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -338,7 +338,7 @@
   _f() => x = 0;
 }
 ''');
-    visitAssignmentTarget(findNode.simple('x '), 'int?', 'int?');
+    visitAssignmentTarget(findNode.simple('x '), null, 'int?');
   }
 
   test_binaryExpression_ampersand_ampersand() async {
@@ -1203,9 +1203,14 @@
       {Set<Expression> nullChecked = const <Expression>{},
       Map<AstNode, Set<Problem>> problems = const <AstNode, Set<Problem>>{}}) {
     _FixBuilder fixBuilder = _createFixBuilder(node);
-    var targetInfo = fixBuilder.visitAssignmentTarget(node);
-    expect((targetInfo.readType as TypeImpl).toString(withNullability: true),
-        expectedReadType);
+    var targetInfo =
+        fixBuilder.visitAssignmentTarget(node, expectedReadType != null);
+    if (expectedReadType == null) {
+      expect(targetInfo.readType, null);
+    } else {
+      expect((targetInfo.readType as TypeImpl).toString(withNullability: true),
+          expectedReadType);
+    }
     expect((targetInfo.writeType as TypeImpl).toString(withNullability: true),
         expectedWriteType);
     expect(fixBuilder.nullCheckedExpressions, nullChecked);