Don't force target type equality for null-aware extension method calls

Null-aware calls, like `v?.extMethod()` don't require `v` to be
non-nullable, even if `extMethod` was declared on a non-nullable type.

Tested: added a new test.
Change-Id: I113ecde21e480bcd467367d0954b721c2ad9f7e0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301982
Reviewed-by: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index a4f4b8e..18d0d99 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -281,7 +281,9 @@
   /// [targetExpression], if provided, is the expression for the target
   /// (receiver) for a method, getter, or setter invocation.
   DecoratedType getOrComputeElementType(AstNode node, Element element,
-      {DecoratedType? targetType, Expression? targetExpression}) {
+      {DecoratedType? targetType,
+      Expression? targetExpression,
+      bool isNullAware = false}) {
     Map<TypeParameterElement, DecoratedType>? substitution;
     Element? baseElement = element.declaration;
     if (targetType != null) {
@@ -347,8 +349,9 @@
             FixReasonTarget.root,
             source: targetType,
             destination: onType,
-            hard: targetExpression == null ||
-                _isReferenceInScope(targetExpression));
+            hard: !isNullAware &&
+                (targetExpression == null ||
+                    _isReferenceInScope(targetExpression)));
         // (3) substitute those decorated types into the declared type of the
         // extension method or extension property, so that the caller will match
         // up parameter types and the return type appropriately.
@@ -1444,7 +1447,9 @@
       calleeType = targetType;
     } else if (callee != null) {
       calleeType = getOrComputeElementType(node, callee,
-          targetType: targetType, targetExpression: target);
+          targetType: targetType,
+          targetExpression: target,
+          isNullAware: isNullAware);
       if (callee is PropertyAccessorElement) {
         calleeType = calleeType.returnType;
       }
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 736f517..d3e3738 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -1183,6 +1183,32 @@
     });
   }
 
+  Future<void> test_call_already_migrated_extension_null_aware() async {
+    var content = '''
+import 'already_migrated.dart';
+class C {
+  X m(V v) => v?.toX();
+}
+''';
+    var alreadyMigrated = '''
+// @dart=2.12
+class X {}
+class V {}
+extension Ext on V {
+  X toX() => X();
+}
+''';
+    var expected = '''
+import 'already_migrated.dart';
+class C {
+  X? m(V? v) => v?.toX();
+}
+''';
+    await _checkSingleFileChanges(content, expected, migratedInput: {
+      '$projectPath/lib/already_migrated.dart': alreadyMigrated
+    });
+  }
+
   Future<void> test_call_generic_function_returns_generic_class() async {
     var content = '''
 class B<E> implements List<E/*?*/> {