Version 2.16.0-71.0.dev

Merge commit 'e729bdab37405781645549eec30f71ebe05f84ee' into 'dev'
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index e2c1264..0fc0c32 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -84,6 +84,9 @@
   }
 
   @override
+  DecoratedType? _getCallMethodType(DecoratedType type) => null;
+
+  @override
   DecoratedType _getTypeParameterTypeBound(DecoratedType type) {
     return bounds[(type.type as TypeParameterType).element] ??
         (throw StateError('Unknown bound for $type'));
@@ -241,8 +244,10 @@
   /// assigned to so far inside it.  Otherwise `null`.
   Set<Element>? _elementsWrittenToInLocalFunction;
 
+  final LibraryElement _library;
+
   EdgeBuilder(this.typeProvider, this._typeSystem, this._variables, this._graph,
-      this.source, this.listener, this._decoratedClassHierarchy,
+      this.source, this.listener, this._decoratedClassHierarchy, this._library,
       {this.instrumentation})
       : _inheritanceManager = InheritanceManager3(),
         _whereOrNullTransformer =
@@ -2365,6 +2370,20 @@
           typeArguments: [type]);
 
   @override
+  DecoratedType? _getCallMethodType(DecoratedType type) {
+    var typeType = type.type;
+    if (typeType is InterfaceType) {
+      var callMethod = typeType.lookUpMethod2('call', _library);
+      if (callMethod != null) {
+        return _variables!
+            .decoratedElementType(callMethod.declaration)
+            .substitute(type.asSubstitution);
+      }
+    }
+    return null;
+  }
+
+  @override
   DecoratedType? _getTypeParameterTypeBound(DecoratedType type) {
     // TODO(paulberry): once we've wired up flow analysis, return promoted
     // bounds if applicable.
@@ -3528,6 +3547,19 @@
             source: source, destination: destination, hard: hard);
         return;
       }
+      if (destinationType is FunctionType) {
+        var callMethodType = _getCallMethodType(source);
+        if (callMethodType != null) {
+          // Handle implicit `.call` coercion
+          _checkAssignment(origin, edgeTarget,
+              source: callMethodType,
+              destination: destination,
+              hard: false,
+              checkable: false,
+              sourceIsFunctionLiteral: sourceIsFunctionLiteral);
+          return;
+        }
+      }
       // A side cast. This may be an explicit side cast, or illegal code. There
       // is no nullability we can infer here.
       assert(
@@ -3798,6 +3830,11 @@
       EdgeOrigin origin, FixReasonTarget edgeTarget,
       {bool hard = false, bool checkable = true});
 
+  /// If [type] represents a class containing a `call` method, returns the
+  /// decorated type of the `call` method, with appropriate substitutions.
+  /// Otherwise returns `null`.
+  DecoratedType? _getCallMethodType(DecoratedType type);
+
   /// Given a [type] representing a type parameter, retrieves the type's bound.
   DecoratedType? _getTypeParameterTypeBound(DecoratedType type);
 }
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index 44f4d37..0caee26 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -672,6 +672,31 @@
     return null;
   }
 
+  bool _isSubtypeOrCoercible(DartType type, DartType context) {
+    var fixBuilder = _fixBuilder!;
+    var typeSystem = fixBuilder._typeSystem;
+    if (typeSystem.isSubtypeOf(type, context)) {
+      return true;
+    }
+    if (context is FunctionType && type is InterfaceType) {
+      var callMethod =
+          type.lookUpMethod2('call', fixBuilder.unit!.declaredElement!.library);
+      if (callMethod != null) {
+        var variables = fixBuilder._variables!;
+        var callMethodType = variables.toFinalType(
+            variables.decoratedElementType(callMethod.declaration));
+        if (callMethod is MethodMember) {
+          callMethodType =
+              callMethod.substitution.substituteType(callMethodType);
+        }
+        if (typeSystem.isSubtypeOf(callMethodType, context)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   DartType _modifyRValueType(Expression node, DartType type,
       {DartType? context}) {
     if (node is MethodInvocation) {
@@ -697,7 +722,7 @@
     var ancestor = _findNullabilityContextAncestor(node);
     context ??=
         InferenceContext.getContext(ancestor) ?? DynamicTypeImpl.instance;
-    if (!_fixBuilder!._typeSystem.isSubtypeOf(type, context)) {
+    if (!_isSubtypeOrCoercible(type, context)) {
       var transformationInfo =
           _fixBuilder!._whereOrNullTransformer.tryTransformOrElseArgument(node);
       if (transformationInfo != null) {
diff --git a/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart b/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart
index 867f72a..a9d155e 100644
--- a/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart
+++ b/pkg/nnbd_migration/lib/src/nullability_migration_impl.dart
@@ -236,6 +236,7 @@
           unit.declaredElement!.source,
           _permissive! ? listener : null,
           _decoratedClassHierarchy,
+          result.libraryElement,
           instrumentation: _instrumentation));
     } finally {
       DecoratedTypeParameterBounds.current = null;
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 286aa4e..182d33a 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -877,6 +877,135 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  Future<void> test_call_tearoff() async {
+    var content = '''
+class C {
+  void call() {}
+}
+void Function() f(C c) => c;
+''';
+    var expected = '''
+class C {
+  void call() {}
+}
+void Function() f(C c) => c;
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_call_tearoff_already_migrated() async {
+    var content = '''
+import 'already_migrated.dart';
+void Function() f(C c) => c;
+''';
+    var alreadyMigrated = '''
+// @dart=2.12
+class C {
+  void call() {}
+}
+''';
+    var expected = '''
+import 'already_migrated.dart';
+void Function() f(C c) => c;
+''';
+    await _checkSingleFileChanges(content, expected, migratedInput: {
+      '$projectPath/lib/already_migrated.dart': alreadyMigrated
+    });
+  }
+
+  Future<void>
+      test_call_tearoff_already_migrated_propagate_nullability() async {
+    var content = '''
+import 'already_migrated.dart';
+Map<int, String> Function() f(C c) => c;
+''';
+    var alreadyMigrated = '''
+// @dart=2.12
+class C {
+  Map<int, String?> call() => {};
+}
+''';
+    var expected = '''
+import 'already_migrated.dart';
+Map<int, String?> Function() f(C c) => c;
+''';
+    await _checkSingleFileChanges(content, expected, migratedInput: {
+      '$projectPath/lib/already_migrated.dart': alreadyMigrated
+    });
+  }
+
+  Future<void> test_call_tearoff_already_migrated_with_substitution() async {
+    var content = '''
+import 'already_migrated.dart';
+Map<int, String> Function() f(C<String/*?*/> c) => c;
+''';
+    var alreadyMigrated = '''
+// @dart=2.12
+class C<T> {
+  Map<int, T> call() => {};
+}
+''';
+    var expected = '''
+import 'already_migrated.dart';
+Map<int, String?> Function() f(C<String?> c) => c;
+''';
+    await _checkSingleFileChanges(content, expected, migratedInput: {
+      '$projectPath/lib/already_migrated.dart': alreadyMigrated
+    });
+  }
+
+  Future<void> test_call_tearoff_inherited() async {
+    var content = '''
+class B {
+  void call() {}
+}
+class C extends B {}
+void Function() f(C c) => c;
+''';
+    var expected = '''
+class B {
+  void call() {}
+}
+class C extends B {}
+void Function() f(C c) => c;
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_call_tearoff_inherited_propagate_nullability() async {
+    var content = '''
+class B {
+  Map<int, String> call() => {1: null};
+}
+class C extends B {}
+Map<int, String> Function() f(C c) => c;
+''';
+    var expected = '''
+class B {
+  Map<int, String?> call() => {1: null};
+}
+class C extends B {}
+Map<int, String?> Function() f(C c) => c;
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_call_tearoff_propagate_nullability() async {
+    var content = '''
+class C {
+  Map<int, String> call() => {1: null};
+}
+Map<int, String> Function() f(C c) => c;
+''';
+    var expected = '''
+class C {
+  Map<int, String?> call() => {1: null};
+}
+Map<int, String?> Function() f(C c) => c;
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   Future<void> test_catch_simple() async {
     var content = '''
 void f() {
@@ -4938,6 +5067,18 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  Future<void> test_implicit_tearoff_type_arguments() async {
+    var content = '''
+T f<T>(T t) => t;
+int Function(int) g() => f;
+''';
+    var expected = '''
+T f<T>(T t) => t;
+int Function(int) g() => f;
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   Future<void> test_implicit_type_parameter_bound_nullable() async {
     var content = '''
 class C<T> {
@@ -5183,6 +5324,16 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  Future<void> test_int_double_coercion() async {
+    var content = '''
+double f() => 0;
+''';
+    var expected = '''
+double f() => 0;
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   Future<void> test_is_promotion_implies_non_nullable() async {
     var content = '''
 bool f(Object o) => o is int && o.isEven;
diff --git a/pkg/nnbd_migration/test/migration_visitor_test_base.dart b/pkg/nnbd_migration/test/migration_visitor_test_base.dart
index 9a4294b..85448d0 100644
--- a/pkg/nnbd_migration/test/migration_visitor_test_base.dart
+++ b/pkg/nnbd_migration/test/migration_visitor_test_base.dart
@@ -147,8 +147,15 @@
   Future<CompilationUnit> analyze(String code) async {
     var unit = await super.analyze(code);
     decoratedClassHierarchy = DecoratedClassHierarchy(variables, graph);
-    unit.accept(EdgeBuilder(typeProvider, typeSystem, variables, graph,
-        testSource, null, decoratedClassHierarchy));
+    unit.accept(EdgeBuilder(
+        typeProvider,
+        typeSystem,
+        variables,
+        graph,
+        testSource,
+        null,
+        decoratedClassHierarchy,
+        unit.declaredElement!.library));
     return unit;
   }
 }
diff --git a/tools/VERSION b/tools/VERSION
index ae9a195..9dfdd80 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 70
+PRERELEASE 71
 PRERELEASE_PATCH 0
\ No newline at end of file