Migration: add FixBuilder support for prefix/postfix expressions.

Change-Id: I62a705af1c8d9944a417d074373aca4c93e99b6d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121402
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 cf44fad..f3abc57 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -335,6 +335,49 @@
   }
 
   @override
+  DartType visitPostfixExpression(PostfixExpression node) {
+    if (node.operator.type == TokenType.BANG) {
+      throw UnimplementedError(
+          'TODO(paulberry): re-migration of already migrated code not '
+          'supported yet');
+    } else {
+      var targetInfo = visitAssignmentTarget(node.operand);
+      _handleIncrementOrDecrement(node.staticElement, targetInfo, node);
+      return targetInfo.readType;
+    }
+  }
+
+  @override
+  DartType visitPrefixExpression(PrefixExpression node) {
+    var operand = node.operand;
+    switch (node.operator.type) {
+      case TokenType.BANG:
+        visitSubexpression(operand, _typeProvider.boolType);
+        _flowAnalysis.logicalNot_end(node, operand);
+        return _typeProvider.boolType;
+      case TokenType.MINUS:
+      case TokenType.TILDE:
+        var targetType = visitSubexpression(operand, _typeProvider.objectType);
+        var staticElement = node.staticElement;
+        if (staticElement == null) {
+          return _typeProvider.dynamicType;
+        } else {
+          var methodType =
+              _computeMigratedType(staticElement, targetType: targetType)
+                  as FunctionType;
+          return methodType.returnType;
+        }
+        break;
+      case TokenType.PLUS_PLUS:
+      case TokenType.MINUS_MINUS:
+        return _handleIncrementOrDecrement(
+            node.staticElement, visitAssignmentTarget(operand), node);
+      default:
+        throw StateError('Unexpected prefix operator: ${node.operator}');
+    }
+  }
+
+  @override
   DartType visitSimpleIdentifier(SimpleIdentifier node) {
     assert(!node.inSetterContext(),
         'Should use visitAssignmentTarget in setter contexts');
@@ -496,6 +539,26 @@
     }
   }
 
+  DartType _handleIncrementOrDecrement(MethodElement combiner,
+      AssignmentTargetInfo targetInfo, Expression node) {
+    DartType combinedType;
+    if (combiner == null) {
+      combinedType = _typeProvider.dynamicType;
+    } else {
+      if (_typeSystem.isNullable(targetInfo.readType)) {
+        addProblem(node, const CompoundAssignmentReadNullable());
+      }
+      var combinerType = _computeMigratedType(combiner) as FunctionType;
+      combinedType = _fixNumericTypes(combinerType.returnType, node.staticType);
+    }
+    if (_doesAssignmentNeedCheck(
+        from: combinedType, to: targetInfo.writeType)) {
+      addProblem(node, const CompoundAssignmentCombinedNullable());
+      combinedType = _typeSystem.promoteToNonNull(combinedType as TypeImpl);
+    }
+    return combinedType;
+  }
+
   /// Visits all the type arguments in a [TypeArgumentList] and returns the
   /// types they ger migrated to.
   List<DartType> _visitTypeArgumentList(TypeArgumentList arguments) =>
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index b7499b5..f25ba0b 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -698,6 +698,260 @@
     visitSubexpression(findNode.binary('&&'), 'bool');
   }
 
+  test_postfixExpression_combined_nullable_noProblem() async {
+    await analyze('''
+abstract class _C {
+  _D/*?*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+abstract class _E {
+  _C/*!*/ get x;
+  void set x(_C/*?*/ value);
+  f() => x++;
+}
+''');
+    visitSubexpression(findNode.postfix('++'), '_C');
+  }
+
+  @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/38833')
+  test_postfixExpression_combined_nullable_noProblem_dynamic() async {
+    await analyze('''
+abstract class _E {
+  dynamic get x;
+  void set x(Object/*!*/ value);
+  f() => x++;
+}
+''');
+    visitSubexpression(findNode.postfix('++'), 'dynamic');
+  }
+
+  test_postfixExpression_combined_nullable_problem() async {
+    await analyze('''
+abstract class _C {
+  _D/*?*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+abstract class _E {
+  _C/*!*/ get x;
+  void set x(_C/*!*/ value);
+  f() => x++;
+}
+''');
+    var postfix = findNode.postfix('++');
+    visitSubexpression(postfix, '_C', problems: {
+      postfix: {const CompoundAssignmentCombinedNullable()}
+    });
+  }
+
+  test_postfixExpression_dynamic() async {
+    await analyze('''
+_f(dynamic x) => x++;
+''');
+    visitSubexpression(findNode.postfix('++'), 'dynamic');
+  }
+
+  test_postfixExpression_lhs_nullable_problem() async {
+    await analyze('''
+abstract class _C {
+  _D/*!*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+abstract class _E {
+  _C/*?*/ get x;
+  void set x(_C/*?*/ value);
+  f() => x++;
+}
+''');
+    var postfix = findNode.postfix('++');
+    visitSubexpression(postfix, '_C?', problems: {
+      postfix: {const CompoundAssignmentReadNullable()}
+    });
+  }
+
+  test_postfixExpression_rhs_nonNullable() async {
+    await analyze('''
+abstract class _C {
+  _D/*!*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+_f(_C/*!*/ x) => x++;
+''');
+    visitSubexpression(findNode.postfix('++'), '_C');
+  }
+
+  test_prefixExpression_bang_flow() async {
+    await analyze('''
+_f(int/*?*/ x) {
+  if (!(x == null)) {
+    x + 1;
+  }
+}
+''');
+    // No null check should be needed on `x + 1` because `!(x == null)` promotes
+    // x's type to `int`.
+    visitStatement(findNode.statement('if'));
+  }
+
+  test_prefixExpression_bang_nonNullable() async {
+    await analyze('''
+_f(bool/*!*/ x) => !x;
+''');
+    visitSubexpression(findNode.prefix('!x'), 'bool');
+  }
+
+  test_prefixExpression_bang_nullable() async {
+    await analyze('''
+_f(bool/*?*/ x) => !x;
+''');
+    visitSubexpression(findNode.prefix('!x'), 'bool',
+        nullChecked: {findNode.simple('x;')});
+  }
+
+  test_prefixExpression_combined_nullable_noProblem() async {
+    await analyze('''
+abstract class _C {
+  _D/*?*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+abstract class _E {
+  _C/*!*/ get x;
+  void set x(_C/*?*/ value);
+  f() => ++x;
+}
+''');
+    visitSubexpression(findNode.prefix('++'), '_D?');
+  }
+
+  test_prefixExpression_combined_nullable_noProblem_dynamic() async {
+    await analyze('''
+abstract class _E {
+  dynamic get x;
+  void set x(Object/*!*/ value);
+  f() => ++x;
+}
+''');
+    var prefix = findNode.prefix('++');
+    visitSubexpression(prefix, 'dynamic');
+  }
+
+  test_prefixExpression_combined_nullable_problem() async {
+    await analyze('''
+abstract class _C {
+  _D/*?*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+abstract class _E {
+  _C/*!*/ get x;
+  void set x(_C/*!*/ value);
+  f() => ++x;
+}
+''');
+    var prefix = findNode.prefix('++');
+    visitSubexpression(prefix, '_D', problems: {
+      prefix: {const CompoundAssignmentCombinedNullable()}
+    });
+  }
+
+  test_prefixExpression_intRules() async {
+    await analyze('''
+_f(int x) => ++x;
+''');
+    visitSubexpression(findNode.prefix('++'), 'int');
+  }
+
+  test_prefixExpression_lhs_nullable_problem() async {
+    await analyze('''
+abstract class _C {
+  _D/*!*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+abstract class _E {
+  _C/*?*/ get x;
+  void set x(_C/*?*/ value);
+  f() => ++x;
+}
+''');
+    var prefix = findNode.prefix('++');
+    visitSubexpression(prefix, '_D', problems: {
+      prefix: {const CompoundAssignmentReadNullable()}
+    });
+  }
+
+  test_prefixExpression_minus_dynamic() async {
+    await analyze('''
+_f(dynamic x) => -x;
+''');
+    visitSubexpression(findNode.prefix('-x'), 'dynamic');
+  }
+
+  test_prefixExpression_minus_nonNullable() async {
+    await analyze('''
+_f(int/*!*/ x) => -x;
+''');
+    visitSubexpression(findNode.prefix('-x'), 'int');
+  }
+
+  test_prefixExpression_minus_nullable() async {
+    await analyze('''
+_f(int/*?*/ x) => -x;
+''');
+    visitSubexpression(findNode.prefix('-x'), 'int',
+        nullChecked: {findNode.simple('x;')});
+  }
+
+  test_prefixExpression_minus_substitution() async {
+    await analyze('''
+abstract class _C<T> {
+  List<T> operator-();
+}
+_f(_C<int> x) => -x;
+''');
+    visitSubexpression(findNode.prefix('-x'), 'List<int>');
+  }
+
+  test_prefixExpression_rhs_nonNullable() async {
+    await analyze('''
+abstract class _C {
+  _D/*!*/ operator+(int/*!*/ value);
+}
+abstract class _D extends _C {}
+_f(_C/*!*/ x) => ++x;
+''');
+    visitSubexpression(findNode.prefix('++'), '_D');
+  }
+
+  test_prefixExpression_tilde_dynamic() async {
+    await analyze('''
+_f(dynamic x) => ~x;
+''');
+    visitSubexpression(findNode.prefix('~x'), 'dynamic');
+  }
+
+  test_prefixExpression_tilde_nonNullable() async {
+    await analyze('''
+_f(int/*!*/ x) => ~x;
+''');
+    visitSubexpression(findNode.prefix('~x'), 'int');
+  }
+
+  test_prefixExpression_tilde_nullable() async {
+    await analyze('''
+_f(int/*?*/ x) => ~x;
+''');
+    visitSubexpression(findNode.prefix('~x'), 'int',
+        nullChecked: {findNode.simple('x;')});
+  }
+
+  test_prefixExpression_tilde_substitution() async {
+    await analyze('''
+abstract class _C<T> {
+  List<T> operator~();
+}
+_f(_C<int> x) => ~x;
+''');
+    visitSubexpression(findNode.prefix('~x'), 'List<int>');
+  }
+
   test_simpleIdentifier_className() async {
     await analyze('''
 _f() => int;