Migration: finish EdgeBuilder support for prefix/postfix expressions.

A few changes here:

- Prefix expressions didn't support dynamic invocations.

- Prefix expressions didn't support generic types.

- Postfix expressions were handled incorrectly (we used the return
  type of operator+ for `x++`, whereas the type should have been the
  same as the type of `x`).

Change-Id: I35077b82a6e9c4e4f6ad8bb1264f5d725ec0d9a9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121401
Reviewed-by: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index f08cb75..cc52881 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -983,21 +983,7 @@
     var operatorType = node.operator.type;
     if (operatorType == TokenType.PLUS_PLUS ||
         operatorType == TokenType.MINUS_MINUS) {
-      _checkExpressionNotNull(node.operand);
-      var callee = node.staticElement;
-      if (callee is ClassMemberElement &&
-          (callee.enclosingElement as ClassElement).typeParameters.isNotEmpty) {
-        // TODO(paulberry)
-        _unimplemented(node,
-            'Operator ${operatorType.lexeme} defined on a class with type parameters');
-      }
-      if (callee == null) {
-        // TODO(paulberry)
-        _unimplemented(node, 'Unresolved operator ${operatorType.lexeme}');
-      }
-      var calleeType = getOrComputeElementType(callee);
-      // TODO(paulberry): substitute if necessary
-      return _fixNumericTypes(calleeType.returnType, node.staticType);
+      return _checkExpressionNotNull(node.operand);
     }
     _unimplemented(
         node, 'Postfix expression with operator ${node.operator.lexeme}');
@@ -1020,26 +1006,22 @@
     if (operatorType == TokenType.BANG) {
       _flowAnalysis.logicalNot_end(node, node.operand);
       return _nonNullableBoolType;
-    } else if (operatorType == TokenType.PLUS_PLUS ||
-        operatorType == TokenType.MINUS_MINUS) {
-      var callee = node.staticElement;
-      if (callee is ClassMemberElement &&
-          (callee.enclosingElement as ClassElement).typeParameters.isNotEmpty) {
-        // TODO(paulberry)
-        _unimplemented(node,
-            'Operator ${operatorType.lexeme} defined on a class with type parameters');
-      }
-      if (callee == null) {
-        // TODO(paulberry)
-        _unimplemented(node, 'Unresolved operator ${operatorType.lexeme}');
-      }
-      var calleeType = getOrComputeElementType(callee);
-      // TODO(paulberry): substitute if necessary
-      return _fixNumericTypes(calleeType.returnType, node.staticType);
     } else {
       var callee = node.staticElement;
+      if (callee == null) {
+        // Dynamic dispatch.  The return type is `dynamic`.
+        // TODO(paulberry): would it be better to assume a return type of `Never`
+        // so that we don't unnecessarily propagate nullabilities everywhere?
+        return _dynamicType;
+      }
       var calleeType = getOrComputeElementType(callee, targetType: targetType);
-      return _handleInvocationArguments(node, [], null, null, calleeType, null);
+      if (operatorType == TokenType.PLUS_PLUS ||
+          operatorType == TokenType.MINUS_MINUS) {
+        return _fixNumericTypes(calleeType.returnType, node.staticType);
+      } else {
+        return _handleInvocationArguments(
+            node, [], null, null, calleeType, null);
+      }
     }
   }
 
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index f5ad4c3..230ac17 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -4221,7 +4221,7 @@
     assertNullCheck(use, assertEdge(declaration, never, hard: true));
 
     var returnType = decoratedTypeAnnotation('int f').node;
-    assertEdge(never, returnType, hard: false);
+    assertEdge(declaration, returnType, hard: false);
   }
 
   test_postfixExpression_plusPlus() async {
@@ -4236,7 +4236,37 @@
     assertNullCheck(use, assertEdge(declaration, never, hard: true));
 
     var returnType = decoratedTypeAnnotation('int f').node;
-    assertEdge(never, returnType, hard: false);
+    assertEdge(declaration, returnType, hard: false);
+  }
+
+  test_postfixExpression_plusPlus_dynamic() async {
+    await analyze('''
+Object f(dynamic d) {
+  return d++;
+}
+''');
+    assertEdge(decoratedTypeAnnotation('dynamic d').node,
+        decoratedTypeAnnotation('Object f').node,
+        hard: false);
+  }
+
+  test_postfixExpression_plusPlus_substituted() async {
+    await analyze('''
+abstract class C<T> {
+  C<T> operator+(int x);
+}
+C<int> f(C<int> c) {
+  return c++;
+}
+''');
+
+    var cType = decoratedTypeAnnotation('C<int> c');
+    var returnType = decoratedTypeAnnotation('C<int> f');
+    assertNullCheck(
+        checkExpression('c++'), assertEdge(cType.node, never, hard: true));
+    assertEdge(cType.node, returnType.node, hard: false);
+    assertEdge(cType.typeArguments[0].node, returnType.typeArguments[0].node,
+        hard: false);
   }
 
   test_prefixedIdentifier_field_type() async {
@@ -4339,6 +4369,20 @@
     assertEdge(never, return_f, hard: false);
   }
 
+  test_prefixExpression_bang_dynamic() async {
+    await analyze('''
+Object f(dynamic d) {
+  return !d;
+}
+''');
+
+    var nullable_d = decoratedTypeAnnotation('dynamic d').node;
+    var check_d = checkExpression('d;');
+
+    var return_f = decoratedTypeAnnotation('Object f').node;
+    assertEdge(never, return_f, hard: false);
+  }
+
   test_prefixExpression_minus() async {
     await analyze('''
 abstract class C {
@@ -4353,6 +4397,35 @@
         assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true));
   }
 
+  test_prefixExpression_minus_dynamic() async {
+    await analyze('''
+Object test(dynamic d) => -d;
+''');
+    assertEdge(always, decoratedTypeAnnotation('Object test').node,
+        hard: false);
+    assertEdge(decoratedTypeAnnotation('dynamic d').node, never, hard: true);
+  }
+
+  test_prefixExpression_minus_substituted() async {
+    await analyze('''
+abstract class C<T> {
+  List<T> operator-();
+}
+List<int> test(C<int> c) => -c/*check*/;
+''');
+    var operatorReturnType = decoratedTypeAnnotation('List<T> operator');
+    var cType = decoratedTypeAnnotation('C<int> c');
+    var testReturnType = decoratedTypeAnnotation('List<int> test');
+    assertEdge(operatorReturnType.node, testReturnType.node, hard: false);
+    assertNullCheck(checkExpression('c/*check*/'),
+        assertEdge(cType.node, never, hard: true));
+    assertEdge(
+        substitutionNode(cType.typeArguments[0].node,
+            operatorReturnType.typeArguments[0].node),
+        testReturnType.typeArguments[0].node,
+        hard: false);
+  }
+
   test_prefixExpression_minusMinus() async {
     await analyze('''
 int f(int i) {
@@ -4383,6 +4456,35 @@
     assertEdge(never, returnType, hard: false);
   }
 
+  test_prefixExpression_plusPlus_dynamic() async {
+    await analyze('''
+Object f(dynamic d) {
+  return ++d;
+}
+''');
+    var returnType = decoratedTypeAnnotation('Object f').node;
+    assertEdge(always, returnType, hard: false);
+  }
+
+  test_prefixExpression_plusPlus_substituted() async {
+    await analyze('''
+abstract class C<T> {
+  C<T> operator+(int i);
+}
+C<int> f(C<int> x) => ++x;
+    ''');
+    var xType = decoratedTypeAnnotation('C<int> x');
+    var plusReturnType = decoratedTypeAnnotation('C<T> operator');
+    var fReturnType = decoratedTypeAnnotation('C<int> f');
+    assertEdge(xType.node, never, hard: true);
+    assertEdge(plusReturnType.node, fReturnType.node, hard: false);
+    assertEdge(
+        substitutionNode(
+            xType.typeArguments[0].node, plusReturnType.typeArguments[0].node),
+        fReturnType.typeArguments[0].node,
+        hard: false);
+  }
+
   test_propertyAccess_dynamic() async {
     await analyze('''
 class C {