Migration: Add IndexExpression support to FixBuilder.

Change-Id: I870047d12304c70cfe89e64d4864cc1809b7e4ac
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121408
Commit-Queue: Paul Berry <paulberry@google.com>
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 6322c06..a9fd486 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -179,6 +179,34 @@
           ? writeType
           : _computeMigratedType(auxiliaryElements.staticElement);
       return AssignmentTargetInfo(isCompound ? readType : null, writeType);
+    } else if (node is IndexExpression) {
+      var targetType =
+          visitSubexpression(node.target, _typeProvider.objectType);
+      var writeElement = node.staticElement;
+      DartType indexContext;
+      DartType writeType;
+      DartType readType;
+      if (writeElement == null) {
+        indexContext = UnknownInferredType.instance;
+        writeType = _typeProvider.dynamicType;
+        readType = isCompound ? _typeProvider.dynamicType : null;
+      } else {
+        var writerType =
+            _computeMigratedType(writeElement, targetType: targetType)
+                as FunctionType;
+        writeType = writerType.parameters[1].type;
+        if (isCompound) {
+          var readerType = _computeMigratedType(
+              node.auxiliaryElements.staticElement,
+              targetType: targetType) as FunctionType;
+          readType = readerType.returnType;
+          indexContext = readerType.parameters[0].type;
+        } else {
+          indexContext = writerType.parameters[0].type;
+        }
+      }
+      visitSubexpression(node.index, indexContext);
+      return AssignmentTargetInfo(readType, writeType);
     } else {
       throw UnimplementedError('TODO(paulberry)');
     }
@@ -277,6 +305,28 @@
   }
 
   @override
+  DartType visitIndexExpression(IndexExpression node) {
+    var target = node.target;
+    var staticElement = node.staticElement;
+    var index = node.index;
+    var targetType = visitSubexpression(target, _typeProvider.objectType);
+    DartType contextType;
+    DartType returnType;
+    if (staticElement == null) {
+      contextType = _typeProvider.dynamicType;
+      returnType = _typeProvider.dynamicType;
+    } else {
+      var methodType =
+          _computeMigratedType(staticElement, targetType: targetType)
+              as FunctionType;
+      contextType = methodType.parameters[0].type;
+      returnType = methodType.returnType;
+    }
+    visitSubexpression(index, contextType);
+    return returnType;
+  }
+
+  @override
   DartType visitListLiteral(ListLiteral node) {
     DartType contextType;
     var typeArguments = node.typeArguments;
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index 143c4a7..251a3805 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -236,6 +236,158 @@
     visitSubexpression(findNode.binary('&&'), 'bool');
   }
 
+  test_assignmentTarget_indexExpression_compound_dynamic() async {
+    await analyze('''
+_f(dynamic d, int/*?*/ i) => d[i] += 0;
+''');
+    visitAssignmentTarget(findNode.index('d[i]'), 'dynamic', 'dynamic');
+  }
+
+  test_assignmentTarget_indexExpression_compound_simple() async {
+    await analyze('''
+class _C {
+  int operator[](String s) => 1;
+  void operator[]=(String s, num n) {}
+}
+_f(_C c) => c['foo'] += 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), 'int', 'num');
+  }
+
+  test_assignmentTarget_indexExpression_compound_simple_check_lhs() async {
+    await analyze('''
+class _C {
+  int operator[](String s) => 1;
+  void operator[]=(String s, num n) {}
+}
+_f(_C/*?*/ c) => c['foo'] += 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), 'int', 'num',
+        nullChecked: {findNode.simple('c[')});
+  }
+
+  test_assignmentTarget_indexExpression_compound_simple_check_rhs() async {
+    await analyze('''
+class _C {
+  int operator[](String/*!*/ s) => 1;
+  void operator[]=(String/*?*/ s, num n) {}
+}
+_f(_C c, String/*?*/ s) => c[s] += 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), 'int', 'num',
+        nullChecked: {findNode.simple('s]')});
+  }
+
+  test_assignmentTarget_indexExpression_compound_substituted() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+  void operator[]=(U u, T t) {}
+}
+_f(_C<int, String> c) => c['foo'] += 1;
+''');
+    visitAssignmentTarget(findNode.index('c['), 'int', 'int');
+  }
+
+  test_assignmentTarget_indexExpression_compound_substituted_check_rhs() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+  void operator[]=(U/*?*/ u, T t) {}
+}
+_f(_C<int, String/*!*/> c, String/*?*/ s) => c[s] += 1;
+''');
+    visitAssignmentTarget(findNode.index('c['), 'int', 'int',
+        nullChecked: {findNode.simple('s]')});
+  }
+
+  test_assignmentTarget_indexExpression_compound_substituted_no_check_rhs() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+  void operator[]=(U u, T t) {}
+}
+_f(_C<int, String/*?*/> c, String/*?*/ s) => c[s] += 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), 'int', 'int');
+  }
+
+  test_assignmentTarget_indexExpression_dynamic() async {
+    await analyze('''
+_f(dynamic d, int/*?*/ i) => d[i] = 0;
+''');
+    visitAssignmentTarget(findNode.index('d[i]'), null, 'dynamic');
+  }
+
+  test_assignmentTarget_indexExpression_simple() async {
+    await analyze('''
+class _C {
+  int operator[](String s) => 1;
+  void operator[]=(String s, num n) {}
+}
+_f(_C c) => c['foo'] = 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), null, 'num');
+  }
+
+  test_assignmentTarget_indexExpression_simple_check_lhs() async {
+    await analyze('''
+class _C {
+  int operator[](String s) => 1;
+  void operator[]=(String s, num n) {}
+}
+_f(_C/*?*/ c) => c['foo'] = 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), null, 'num',
+        nullChecked: {findNode.simple('c[')});
+  }
+
+  test_assignmentTarget_indexExpression_simple_check_rhs() async {
+    await analyze('''
+class _C {
+  int operator[](String/*?*/ s) => 1;
+  void operator[]=(String/*!*/ s, num n) {}
+}
+_f(_C c, String/*?*/ s) => c[s] = 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), null, 'num',
+        nullChecked: {findNode.simple('s]')});
+  }
+
+  test_assignmentTarget_indexExpression_substituted() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+  void operator[]=(U u, T t) {}
+}
+_f(_C<int, String> c) => c['foo'] = 1;
+''');
+    visitAssignmentTarget(findNode.index('c['), null, 'int');
+  }
+
+  test_assignmentTarget_indexExpression_substituted_check_rhs() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+  void operator[]=(U/*?*/ u, T t) {}
+}
+_f(_C<int, String/*!*/> c, String/*?*/ s) => c[s] = 1;
+''');
+    visitAssignmentTarget(findNode.index('c['), null, 'int',
+        nullChecked: {findNode.simple('s]')});
+  }
+
+  test_assignmentTarget_indexExpression_substituted_no_check_rhs() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+  void operator[]=(U u, T t) {}
+}
+_f(_C<int, String/*?*/> c, String/*?*/ s) => c[s] = 0;
+''');
+    visitAssignmentTarget(findNode.index('c['), null, 'int');
+  }
+
   test_assignmentTarget_simpleIdentifier_field_generic() async {
     await analyze('''
 abstract class _C<T> {
@@ -645,6 +797,77 @@
     visitStatement(findNode.statement('if'));
   }
 
+  test_indexExpression_dynamic() async {
+    await analyze('''
+Object/*!*/ _f(dynamic d, int/*?*/ i) => d[i];
+''');
+    visitSubexpression(findNode.index('d[i]'), 'dynamic',
+        contextType: objectType);
+  }
+
+  test_indexExpression_simple() async {
+    await analyze('''
+class _C {
+  int operator[](String s) => 1;
+}
+_f(_C c) => c['foo'];
+''');
+    visitSubexpression(findNode.index('c['), 'int');
+  }
+
+  test_indexExpression_simple_check_lhs() async {
+    await analyze('''
+class _C {
+  int operator[](String s) => 1;
+}
+_f(_C/*?*/ c) => c['foo'];
+''');
+    visitSubexpression(findNode.index('c['), 'int',
+        nullChecked: {findNode.simple('c[')});
+  }
+
+  test_indexExpression_simple_check_rhs() async {
+    await analyze('''
+class _C {
+  int operator[](String/*!*/ s) => 1;
+}
+_f(_C c, String/*?*/ s) => c[s];
+''');
+    visitSubexpression(findNode.index('c['), 'int',
+        nullChecked: {findNode.simple('s]')});
+  }
+
+  test_indexExpression_substituted() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+}
+_f(_C<int, String> c) => c['foo'];
+''');
+    visitSubexpression(findNode.index('c['), 'int');
+  }
+
+  test_indexExpression_substituted_check_rhs() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+}
+_f(_C<int, String/*!*/> c, String/*?*/ s) => c[s];
+''');
+    visitSubexpression(findNode.index('c['), 'int',
+        nullChecked: {findNode.simple('s]')});
+  }
+
+  test_indexExpression_substituted_no_check_rhs() async {
+    await analyze('''
+class _C<T, U> {
+  T operator[](U u) => throw 'foo';
+}
+_f(_C<int, String/*?*/> c, String/*?*/ s) => c[s];
+''');
+    visitSubexpression(findNode.index('c['), 'int');
+  }
+
   test_integerLiteral() async {
     await analyze('''
 f() => 1;