Migration: do not union inferred types in overrides.

Previously, when handling an override such as:

  abstract class A {
    int/*?*/ f();
  }
  class C implements A {
    f() => 0;
  }

we would create a union edge between the implicit return type of C.f
and the explicit return type of A.f.  This was a problem because
nullability information can propagate bidirectionally through union
edges, so it could result in types unnecessarily becoming nullable,
e.g.:

  abstract class A {
    int/*?*/ f();
  }
  abstract class B {
    int f(); // Should not need to be made nullable
  }
  class C implements A, B {
    f() => 0;
  }

This CL fixes the problem by just making ordinary unidirectional edges
for overrides involving inferred types.

Change-Id: I63a5f1f640b5543fcf39304087592e984aa66694
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/141853
Commit-Queue: Paul Berry <paulberry@google.com>
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 bba1dd7..a407c0c 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -2209,7 +2209,7 @@
             _currentFunctionType.returnType,
             overriddenFunctionType.returnType,
             ReturnTypeInheritanceOrigin(source, node),
-            isUnion: true);
+            isUnion: false);
       } else {
         _checkAssignment(ReturnTypeInheritanceOrigin(source, node),
             source: _currentFunctionType.returnType,
@@ -2250,7 +2250,7 @@
             if (_isUntypedParameter(normalParameter)) {
               _linkDecoratedTypes(
                   overriddenParameterType, currentParameterType, origin,
-                  isUnion: true);
+                  isUnion: false);
             } else {
               _checkAssignment(origin,
                   source: overriddenParameterType,
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index dd1dc53..7ddd169 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -2643,6 +2643,64 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  Future<void> test_implicit_parameter_type_override_does_not_union() async {
+    var content = '''
+abstract class A {
+  void f(int/*?*/ i);
+}
+abstract class B {
+  void f(int i);
+}
+class C implements A, B {
+  void f(i) {}
+}
+''';
+    // Even though the parameter type of C.f is implicit, its nullability
+    // shouldn't be unioned with that of A and B, because that would
+    // unnecessarily force B.f's parameter type to be nullable.
+    var expected = '''
+abstract class A {
+  void f(int?/*?*/ i);
+}
+abstract class B {
+  void f(int i);
+}
+class C implements A, B {
+  void f(i) {}
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_implicit_return_type_override_does_not_union() async {
+    var content = '''
+abstract class A {
+  int/*?*/ f();
+}
+abstract class B {
+  int f();
+}
+class C implements A, B {
+  f() => 0;
+}
+''';
+    // Even though the return type of C.f is implicit, its nullability shouldn't
+    // be unioned with that of A and B, because that would unnecessarily force
+    // B.f's return type to be nullable.
+    var expected = '''
+abstract class A {
+  int?/*?*/ f();
+}
+abstract class B {
+  int f();
+}
+class C implements A, B {
+  f() => 0;
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   Future<void> test_implicit_type_parameter_bound_nullable() async {
     var content = '''
 class C<T> {
@@ -2686,78 +2744,6 @@
     await _checkSingleFileChanges(content, expected);
   }
 
-  Future<void> test_inferred_method_parameter_type_non_nullable() async {
-    var content = '''
-class B {
-  void f(int i) {
-    assert(i != null);
-  }
-}
-class C extends B {
-  void f(i) {}
-}
-void g(C c, int i, bool b) {
-  if (b) {
-    c.f(i);
-  }
-}
-void h(C c) {
-  g(c, null, false);
-}
-''';
-    // B.f's parameter type is `int`.  Since C.f's parameter type is inferred
-    // from B.f's, it has a parameter type of `int` too.  Therefore there must
-    // be a null check in g().
-    var expected = '''
-class B {
-  void f(int i) {
-    assert(i != null);
-  }
-}
-class C extends B {
-  void f(i) {}
-}
-void g(C c, int? i, bool b) {
-  if (b) {
-    c.f(i!);
-  }
-}
-void h(C c) {
-  g(c, null, false);
-}
-''';
-    await _checkSingleFileChanges(content, expected);
-  }
-
-  Future<void> test_inferred_method_parameter_type_nullable() async {
-    var content = '''
-class B {
-  void f(int i) {}
-}
-class C extends B {
-  void f(i) {}
-}
-void g(C c) {
-  c.f(null);
-}
-''';
-    // The call to C.f from g forces C.f's parameter to be nullable.  Since
-    // C.f's parameter type is inferred from B.f's parameter type, B.f's
-    // parameter must be nullable too.
-    var expected = '''
-class B {
-  void f(int? i) {}
-}
-class C extends B {
-  void f(i) {}
-}
-void g(C c) {
-  c.f(null);
-}
-''';
-    await _checkSingleFileChanges(content, expected);
-  }
-
   Future<void> test_inferred_method_return_type_non_nullable() async {
     var content = '''
 class B {
@@ -2783,31 +2769,6 @@
     await _checkSingleFileChanges(content, expected);
   }
 
-  Future<void> test_inferred_method_return_type_nullable() async {
-    var content = '''
-class B {
-  int f() => null;
-}
-class C extends B {
-  f() => 1;
-}
-int g(C c) => c.f();
-''';
-    // B.f's return type is `int?`.  Since C.f's return type is inferred from
-    // B.f's, it has a return type of `int?` too.  Therefore g's return type
-    // must be `int?`.
-    var expected = '''
-class B {
-  int? f() => null;
-}
-class C extends B {
-  f() => 1;
-}
-int? g(C c) => c.f();
-''';
-    await _checkSingleFileChanges(content, expected);
-  }
-
   Future<void> test_insert_as_prefixed_type() async {
     var content = '''
 import 'dart:async' as a;
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index b529b8c..c75b494 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -4279,7 +4279,7 @@
 ''');
     var bReturnType = decoratedMethodType('f/*B*/').positionalParameters[0];
     var cReturnType = decoratedMethodType('f/*C*/').positionalParameters[0];
-    assertUnion(bReturnType.node, cReturnType.node);
+    assertEdge(bReturnType.node, cReturnType.node, hard: true);
   }
 
   Future<void> test_method_parameterType_inferred_named() async {
@@ -4293,7 +4293,7 @@
 ''');
     var bReturnType = decoratedMethodType('f/*B*/').namedParameters['x'];
     var cReturnType = decoratedMethodType('f/*C*/').namedParameters['x'];
-    assertUnion(bReturnType.node, cReturnType.node);
+    assertEdge(bReturnType.node, cReturnType.node, hard: true);
   }
 
   Future<void> test_method_returnType_inferred() async {
@@ -4307,7 +4307,7 @@
 ''');
     var bReturnType = decoratedMethodType('f/*B*/').returnType;
     var cReturnType = decoratedMethodType('f/*C*/').returnType;
-    assertUnion(bReturnType.node, cReturnType.node);
+    assertEdge(cReturnType.node, bReturnType.node, hard: true);
   }
 
   Future<void>
diff --git a/pkg/nnbd_migration/test/instrumentation_test.dart b/pkg/nnbd_migration/test/instrumentation_test.dart
index d9d01f1..5afbb431 100644
--- a/pkg/nnbd_migration/test/instrumentation_test.dart
+++ b/pkg/nnbd_migration/test/instrumentation_test.dart
@@ -991,8 +991,8 @@
             .node;
     expect(
         edges.where((e) =>
-            e.sourceNode == derivedParamArgNode &&
-            e.destinationNode == baseParamArgNode),
+            e.sourceNode == baseParamArgNode &&
+            e.destinationNode == derivedParamArgNode),
         hasLength(1));
   }