Version 2.14.0-79.0.dev

Merge commit 'bde30978601e0167a5b9261f259c3b4916146881' into 'dev'
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index b687d8c..996f87c 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -877,10 +877,18 @@
           }
           return _emitInterfaceType(t, emitNullability: emitNullability);
         } else if (t is FutureOrType) {
-          _declareBeforeUse(_coreTypes.deprecatedFutureOrClass);
-          // TODO(45870) Add nullability wrappers to FutureOr.
-          return _emitFutureOrTypeWithArgument(
-              _emitDeferredType(t.typeArgument));
+          var normalizedType = _normalizeFutureOr(t);
+          if (normalizedType is FutureOrType) {
+            _declareBeforeUse(_coreTypes.deprecatedFutureOrClass);
+            var typeRep = _emitFutureOrTypeWithArgument(
+                _emitDeferredType(normalizedType.typeArgument));
+            return emitNullability
+                ? _emitNullabilityWrapper(
+                    typeRep, normalizedType.declaredNullability)
+                : typeRep;
+          }
+          return _emitDeferredType(normalizedType,
+              emitNullability: emitNullability);
         } else if (t is TypeParameterType) {
           return _emitTypeParameterType(t, emitNullability: emitNullability);
         }
@@ -2720,16 +2728,19 @@
           ? visitNullType(const NullType())
           : _emitNullabilityWrapper(runtimeCall('Never'), type.nullability);
 
-  /// Normalizes `FutureOr` types and emits the normalized version.
-  js_ast.Expression _normalizeFutureOr(FutureOrType futureOr) {
+  /// Normalizes `FutureOr` types.
+  ///
+  /// Any changes to the normalization logic here should be mirrored in the
+  /// classes.dart runtime library method named `normalizeFutureOr`.
+  DartType _normalizeFutureOr(FutureOrType futureOr) {
     var typeArgument = futureOr.typeArgument;
     if (typeArgument is DynamicType) {
       // FutureOr<dynamic> --> dynamic
-      return visitDynamicType(typeArgument);
+      return typeArgument;
     }
     if (typeArgument is VoidType) {
       // FutureOr<void> --> void
-      return visitVoidType(typeArgument);
+      return typeArgument;
     }
 
     if (typeArgument is InterfaceType &&
@@ -2744,23 +2755,21 @@
           : legacy
               ? Nullability.legacy
               : Nullability.nonNullable;
-      return _emitInterfaceType(
-          typeArgument.withDeclaredNullability(nullability));
+      return typeArgument.withDeclaredNullability(nullability);
     } else if (typeArgument is NeverType) {
       // FutureOr<Never> --> Future<Never>
-      return _emitInterfaceType(InterfaceType(
-          _coreTypes.futureClass, futureOr.nullability, [typeArgument]));
+      return InterfaceType(
+          _coreTypes.futureClass, futureOr.nullability, [typeArgument]);
     } else if (typeArgument is NullType) {
       // FutureOr<Null> --> Future<Null>?
-      return _emitInterfaceType(InterfaceType(
-          _coreTypes.futureClass, Nullability.nullable, [typeArgument]));
+      return InterfaceType(
+          _coreTypes.futureClass, Nullability.nullable, [typeArgument]);
     } else if (futureOr.declaredNullability == Nullability.nullable &&
         typeArgument.nullability == Nullability.nullable) {
       // FutureOr<T?>? --> FutureOr<T?>
-      return _emitFutureOrType(
-          futureOr.withDeclaredNullability(Nullability.nonNullable));
+      return futureOr.withDeclaredNullability(Nullability.nonNullable);
     }
-    return _emitFutureOrType(futureOr);
+    return futureOr;
   }
 
   @override
@@ -2772,8 +2781,12 @@
       type.onType.accept(this);
 
   @override
-  js_ast.Expression visitFutureOrType(FutureOrType type) =>
-      _normalizeFutureOr(type);
+  js_ast.Expression visitFutureOrType(FutureOrType type) {
+    var normalizedType = _normalizeFutureOr(type);
+    return normalizedType is FutureOrType
+        ? _emitFutureOrType(normalizedType)
+        : normalizedType.accept(this);
+  }
 
   /// Emits the representation of [type].
   ///
diff --git a/runtime/lib/function.cc b/runtime/lib/function.cc
index 07e839f..748e056 100644
--- a/runtime/lib/function.cc
+++ b/runtime/lib/function.cc
@@ -54,6 +54,9 @@
         ObjectPtr receiver_a = context_a.At(0);
         ObjectPtr receiver_b = context_b.At(0);
         if ((receiver_a == receiver_b) &&
+            (!func_a.IsGeneric() ||
+             receiver.delayed_type_arguments() ==
+                 other_closure.delayed_type_arguments()) &&
             ((func_a.ptr() == func_b.ptr()) ||
              ((func_a.name() == func_b.name()) &&
               (func_a.Owner() == func_b.Owner())))) {
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 384b853..b013c63 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -24848,8 +24848,14 @@
   uint32_t result = 0;
   if (func.IsImplicitInstanceClosureFunction()) {
     // Implicit instance closures are not unique, so combine function's hash
-    // code with identityHashCode of cached receiver.
+    // code, delayed type arguments hash code (if generic), and identityHashCode
+    // of cached receiver.
     result = static_cast<uint32_t>(func.ComputeClosureHash());
+    if (func.IsGeneric()) {
+      const TypeArguments& delayed_type_args =
+          TypeArguments::Handle(zone, delayed_type_arguments());
+      result = CombineHashes(result, delayed_type_args.Hash());
+    }
     const Context& context = Context::Handle(zone, this->context());
     const Instance& receiver =
         Instance::Handle(zone, Instance::RawCast(context.At(0)));
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart
index ca02b18..7e019bf 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart
@@ -168,7 +168,6 @@
     // this method. This ensures that the we can test a type value returned here
     // as a FutureOr because it is equal to 'async.FutureOr` (in the JS).
     JS('!', '#[#] = #', genericType, _originalDeclaration, normalize);
-    JS('!', '#(#)', addTypeCaches, genericType);
     // Add FutureOr specific is and as methods.
     is_FutureOr(obj) =>
         JS<bool>('!', '#.is(#)', typeArg, obj) ||
@@ -250,7 +249,6 @@
     return value;
   }
   makeGenericType[$_genericTypeCtor] = $typeConstructor;
-  $addTypeCaches(makeGenericType);
   return makeGenericType;
 })()''');
 
diff --git a/tests/language/generic/regress_45767_test.dart b/tests/language/generic/regress_45767_test.dart
index 953c966..11f9bc3 100644
--- a/tests/language/generic/regress_45767_test.dart
+++ b/tests/language/generic/regress_45767_test.dart
@@ -15,11 +15,27 @@
 // Extending F forces DDC to defer the superclass and trigger the bug
 class A<T> extends F<A<Object?>> {}
 
+// Nullability on a type argument in the deferred type.
 class B<T> extends F<B<T?>> {}
 
+// Nullability on a FutureOr in the deferred type.
+class C<T> extends F<C<FutureOr<int>?>> {}
+
+// Reference the current type inside a FutureOr in the deferred type.
+class D<T> extends F<D<FutureOr<D?>>> {}
+
+// Nullability and normalization on FutureOrTypes in the deferred type.
+class G<T> extends F<G<FutureOr<Null>?>> {}
+
 void main() {
   Expect.isTrue(A<bool>() is I<A<Object?>>);
   Expect.equals(!hasSoundNullSafety, A<bool>() is I<A<Object>>);
   Expect.isTrue(B<bool>() is I<B<bool?>>);
   Expect.equals(!hasSoundNullSafety, B<bool>() is I<B<bool>>);
+  Expect.isTrue(C<bool>() is I<C<FutureOr<int>?>>);
+  Expect.equals(!hasSoundNullSafety, C<bool>() is I<C<FutureOr<int>>>);
+  Expect.isTrue(D<bool>() is I<D<FutureOr<D?>>>);
+  Expect.equals(!hasSoundNullSafety, D<bool>() is I<D<FutureOr<D>>>);
+  Expect.isTrue(G<bool>() is I<G<Future<Null>?>>);
+  Expect.equals(!hasSoundNullSafety, G<bool>() is I<G<Future<Null>>>);
 }
diff --git a/tests/language/regress/regress45890_test.dart b/tests/language/regress/regress45890_test.dart
new file mode 100644
index 0000000..abf9a84
--- /dev/null
+++ b/tests/language/regress/regress45890_test.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "package:expect/expect.dart";
+
+class C {
+  T foo<T>(T value) => value;
+}
+
+void main() {
+  var c = C();
+  num Function(num) f0 = c.foo;
+  int Function(int) f1 = c.foo;
+  Expect.notEquals(f0, f1);
+}
diff --git a/tests/language_2/regress/regress45890_test.dart b/tests/language_2/regress/regress45890_test.dart
new file mode 100644
index 0000000..abf9a84
--- /dev/null
+++ b/tests/language_2/regress/regress45890_test.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "package:expect/expect.dart";
+
+class C {
+  T foo<T>(T value) => value;
+}
+
+void main() {
+  var c = C();
+  num Function(num) f0 = c.foo;
+  int Function(int) f1 = c.foo;
+  Expect.notEquals(f0, f1);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 0fb429d..e356ee5 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 78
+PRERELEASE 79
 PRERELEASE_PATCH 0
\ No newline at end of file