Version 2.15.0-38.0.dev

Merge commit '709f87e7f3ac9359eb857e60c5585d32d5266924' into 'dev'
diff --git a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
index d0bfa3e..5626076 100644
--- a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
@@ -188,8 +188,14 @@
       .where((String arg) => !arg.contains('optimization-counter-threshold'))
       .toList();
 
+  // We first compile the testee to kernel and run the subprocess on the kernel
+  // file. That avoids cases where the testee has to run a lot of code in the
+  // kernel-isolate (e.g. due to ia32's kernel-service not being app-jit
+  // trained). We do that because otherwise the --complete-timeline will collect
+  // a lot of data, possibly leading to OOMs or timeouts.
   await runVMTests(args, tests,
       testeeBefore: primeTimeline,
       extraArgs: ['--complete-timeline'],
-      executableArgs: executableArgs);
+      executableArgs: executableArgs,
+      compileToKernelFirst: true);
 }
diff --git a/runtime/observatory/tests/service/test_helper.dart b/runtime/observatory/tests/service/test_helper.dart
index 0369d53..487c098 100644
--- a/runtime/observatory/tests/service/test_helper.dart
+++ b/runtime/observatory/tests/service/test_helper.dart
@@ -7,8 +7,10 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+
 import 'package:dds/dds.dart';
 import 'package:observatory/service_io.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 import 'service_test_common.dart';
 export 'service_test_common.dart' show DDSTest, IsolateTest, VMTest;
@@ -94,7 +96,8 @@
   final _processCompleter = Completer<void>();
   bool killedByTester = false;
 
-  _ServiceTesteeLauncher() : args = [Platform.script.toFilePath()] {}
+  _ServiceTesteeLauncher({String? script})
+      : args = [script ?? Platform.script.toFilePath()] {}
 
   // Spawn the testee process.
   Future<Process> _spawnProcess(
@@ -343,6 +346,7 @@
     bool testeeControlsServer: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
   }) {
     if (executableArgs == null) {
@@ -352,6 +356,7 @@
     late WebSocketVM vm;
     late _ServiceTesteeLauncher process;
     bool testsDone = false;
+    final tempDir = Directory.systemTemp.createTempSync('testee_dill');
 
     ignoreLateException(Function f) async {
       try {
@@ -369,7 +374,26 @@
     setUp(
       () => ignoreLateException(
         () async {
-          process = _ServiceTesteeLauncher();
+          String testeePath = Platform.script.toFilePath();
+          if (compileToKernelFirst && testeePath.endsWith('.dart')) {
+            final testeePathDill = path.join(tempDir.path, 'testee.dill');
+            final ProcessResult result =
+                await Process.run(Platform.executable, [
+              '--snapshot-kind=kernel',
+              '--snapshot=$testeePathDill',
+              ...Platform.executableArguments,
+              testeePath,
+            ]);
+            if (result.exitCode != 0) {
+              throw 'Failed to compile testee to kernel:\n'
+                  'stdout: ${result.stdout}\n'
+                  'stderr: ${result.stderr}\n'
+                  'exitCode: ${result.exitCode}\n';
+            }
+            testeePath = testeePathDill;
+          }
+
+          process = _ServiceTesteeLauncher(script: testeePath);
           await process
               .launch(
                   pause_on_start,
@@ -611,6 +635,7 @@
     bool enable_service_port_fallback: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
     List<String>? extraArgs,
     List<String>? executableArgs}) async {
@@ -633,6 +658,7 @@
       enable_service_port_fallback: enable_service_port_fallback,
       enableDds: enableDds,
       enableService: enableService,
+      compileToKernelFirst: compileToKernelFirst,
       port: port,
     );
   }
diff --git a/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
index 94e2654..0f8e1de 100644
--- a/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
@@ -188,8 +188,14 @@
       .where((String arg) => !arg.contains('optimization-counter-threshold'))
       .toList();
 
+  // We first compile the testee to kernel and run the subprocess on the kernel
+  // file. That avoids cases where the testee has to run a lot of code in the
+  // kernel-isolate (e.g. due to ia32's kernel-service not being app-jit
+  // trained). We do that because otherwise the --complete-timeline will collect
+  // a lot of data, possibly leading to OOMs or timeouts.
   await runVMTests(args, tests,
       testeeBefore: primeTimeline,
       extraArgs: ['--complete-timeline'],
-      executableArgs: executableArgs);
+      executableArgs: executableArgs,
+      compileToKernelFirst: true);
 }
diff --git a/runtime/observatory_2/tests/service_2/test_helper.dart b/runtime/observatory_2/tests/service_2/test_helper.dart
index 570e830..86e7b13 100644
--- a/runtime/observatory_2/tests/service_2/test_helper.dart
+++ b/runtime/observatory_2/tests/service_2/test_helper.dart
@@ -9,8 +9,10 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+
 import 'package:dds/dds.dart';
 import 'package:observatory_2/service_io.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 import 'service_test_common.dart';
 export 'service_test_common.dart' show DDSTest, IsolateTest, VMTest;
@@ -96,7 +98,8 @@
   final _processCompleter = Completer<void>();
   bool killedByTester = false;
 
-  _ServiceTesteeLauncher() : args = [Platform.script.toFilePath()] {}
+  _ServiceTesteeLauncher({String script})
+      : args = [script ?? Platform.script.toFilePath()] {}
 
   // Spawn the testee process.
   Future<Process> _spawnProcess(
@@ -341,6 +344,7 @@
     bool testeeControlsServer: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
   }) {
     if (executableArgs == null) {
@@ -350,6 +354,7 @@
     WebSocketVM vm;
     _ServiceTesteeLauncher process;
     bool testsDone = false;
+    final tempDir = Directory.systemTemp.createTempSync('testee_dill');
 
     ignoreLateException(Function f) async {
       try {
@@ -367,7 +372,26 @@
     setUp(
       () => ignoreLateException(
         () async {
-          process = _ServiceTesteeLauncher();
+          String testeePath = Platform.script.toFilePath();
+          if (compileToKernelFirst && testeePath.endsWith('.dart')) {
+            final testeePathDill = path.join(tempDir.path, 'testee.dill');
+            final ProcessResult result =
+                await Process.run(Platform.executable, [
+              '--snapshot-kind=kernel',
+              '--snapshot=$testeePathDill',
+              ...Platform.executableArguments,
+              testeePath,
+            ]);
+            if (result.exitCode != 0) {
+              throw 'Failed to compile testee to kernel:\n'
+                  'stdout: ${result.stdout}\n'
+                  'stderr: ${result.stderr}\n'
+                  'exitCode: ${result.exitCode}\n';
+            }
+            testeePath = testeePathDill;
+          }
+
+          process = _ServiceTesteeLauncher(script: testeePath);
           await process
               .launch(
                   pause_on_start,
@@ -411,6 +435,7 @@
             await dds?.shutdown();
           }
           process.requestExit();
+          tempDir.deleteSync(recursive: true);
         },
       ),
     );
@@ -609,6 +634,7 @@
     bool enable_service_port_fallback: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
     List<String> extraArgs,
     List<String> executableArgs}) async {
@@ -631,6 +657,7 @@
       enable_service_port_fallback: enable_service_port_fallback,
       enableDds: enableDds,
       enableService: enableService,
+      compileToKernelFirst: compileToKernelFirst,
       port: port,
     );
   }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index d45a059..857404b 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -5497,6 +5497,12 @@
       }
       return true;
     }
+
+    // _Closure <: Function
+    if (this_class.IsClosureClass() && other_class.IsDartFunctionClass()) {
+      return true;
+    }
+
     // Check for 'direct super type' specified in the implements clause
     // and check for transitivity at the same time.
     Array& interfaces = Array::Handle(zone, this_class.interfaces());
@@ -20453,17 +20459,12 @@
     return false;
   }
   // Function types cannot be handled by Class::IsSubtypeOf().
-  if (other.IsDartFunctionType()) {
-    // Any type that can be the type of a closure is a subtype of Function.
-    if (IsDartFunctionType() || IsDartClosureType() || IsFunctionType()) {
-      if (isolate_group->use_strict_null_safety_checks() && IsNullable()) {
-        return !other.IsNonNullable();
-      }
-      return true;
-    }
-    // Fall through.
-  }
   if (IsFunctionType()) {
+    // Any type that can be the type of a closure is a subtype of Function.
+    if (other.IsDartFunctionType()) {
+      return !isolate_group->use_strict_null_safety_checks() || !IsNullable() ||
+             !other.IsNonNullable();
+    }
     if (other.IsFunctionType()) {
       // Check for two function types.
       if (isolate_group->use_strict_null_safety_checks() && IsNullable() &&
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index fa73cff..959f4da 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -5305,13 +5305,108 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(ClosureType_SubtypeOfFunctionType) {
+  auto check_subtype_relation = [](const Expect& expect, const Type& sub,
+                                   const Type& super, bool is_subtype) {
+    if (sub.IsSubtypeOf(super, Heap::kNew) != is_subtype) {
+      TextBuffer buffer(128);
+      buffer.AddString("Expected ");
+      sub.PrintName(Object::kScrubbedName, &buffer);
+      buffer.Printf(" to %s a subtype of ", is_subtype ? "be" : "not be");
+      super.PrintName(Object::kScrubbedName, &buffer);
+      expect.Fail("%s", buffer.buffer());
+    }
+  };
+#define EXPECT_SUBTYPE(sub, super)                                             \
+  check_subtype_relation(Expect(__FILE__, __LINE__), sub, super, true);
+#define EXPECT_NOT_SUBTYPE(sub, super)                                         \
+  check_subtype_relation(Expect(__FILE__, __LINE__), sub, super, false);
+
+  auto finalize_and_canonicalize = [](Type* type) {
+    *type ^= ClassFinalizer::FinalizeType(*type);
+    ASSERT(type->IsCanonical());
+  };
+
   const auto& closure_class =
       Class::Handle(IsolateGroup::Current()->object_store()->closure_class());
   const auto& closure_type = Type::Handle(closure_class.DeclarationType());
+  auto& closure_type_nullable = Type::Handle(
+      closure_type.ToNullability(Nullability::kNullable, Heap::kNew));
+  finalize_and_canonicalize(&closure_type_nullable);
+  auto& closure_type_legacy = Type::Handle(
+      closure_type.ToNullability(Nullability::kLegacy, Heap::kNew));
+  finalize_and_canonicalize(&closure_type_legacy);
+  auto& closure_type_nonnullable = Type::Handle(
+      closure_type.ToNullability(Nullability::kNonNullable, Heap::kNew));
+  finalize_and_canonicalize(&closure_type_nonnullable);
+
   const auto& function_type =
       Type::Handle(IsolateGroup::Current()->object_store()->function_type());
+  auto& function_type_nullable = Type::Handle(
+      function_type.ToNullability(Nullability::kNullable, Heap::kNew));
+  finalize_and_canonicalize(&function_type_nullable);
+  auto& function_type_legacy = Type::Handle(
+      function_type.ToNullability(Nullability::kLegacy, Heap::kNew));
+  finalize_and_canonicalize(&function_type_legacy);
+  auto& function_type_nonnullable = Type::Handle(
+      function_type.ToNullability(Nullability::kNonNullable, Heap::kNew));
+  finalize_and_canonicalize(&function_type_nonnullable);
 
-  EXPECT(closure_type.IsSubtypeOf(function_type, Heap::kNew));
+  EXPECT_SUBTYPE(closure_type_nonnullable, function_type_nullable);
+  EXPECT_SUBTYPE(closure_type_nonnullable, function_type_legacy);
+  EXPECT_SUBTYPE(closure_type_nonnullable, function_type_nonnullable);
+  EXPECT_SUBTYPE(closure_type_legacy, function_type_nullable);
+  EXPECT_SUBTYPE(closure_type_legacy, function_type_legacy);
+  EXPECT_SUBTYPE(closure_type_legacy, function_type_nonnullable);
+  EXPECT_SUBTYPE(closure_type_nullable, function_type_nullable);
+  EXPECT_SUBTYPE(closure_type_nullable, function_type_legacy);
+  // Nullable types are not a subtype of non-nullable types in strict mode.
+  if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
+    EXPECT_NOT_SUBTYPE(closure_type_nullable, function_type_nonnullable);
+  } else {
+    EXPECT_SUBTYPE(closure_type_nullable, function_type_nonnullable);
+  }
+
+  const auto& async_lib = Library::Handle(Library::AsyncLibrary());
+  const auto& future_or_class =
+      Class::Handle(async_lib.LookupClass(Symbols::FutureOr()));
+  auto& tav_function_nullable = TypeArguments::Handle(TypeArguments::New(1));
+  tav_function_nullable.SetTypeAt(0, function_type_nullable);
+  tav_function_nullable = tav_function_nullable.Canonicalize(thread, nullptr);
+  auto& tav_function_legacy = TypeArguments::Handle(TypeArguments::New(1));
+  tav_function_legacy.SetTypeAt(0, function_type_legacy);
+  tav_function_legacy = tav_function_legacy.Canonicalize(thread, nullptr);
+  auto& tav_function_nonnullable = TypeArguments::Handle(TypeArguments::New(1));
+  tav_function_nonnullable.SetTypeAt(0, function_type_nonnullable);
+  tav_function_nonnullable =
+      tav_function_nonnullable.Canonicalize(thread, nullptr);
+
+  auto& future_or_function_type_nullable =
+      Type::Handle(Type::New(future_or_class, tav_function_nullable));
+  finalize_and_canonicalize(&future_or_function_type_nullable);
+  auto& future_or_function_type_legacy =
+      Type::Handle(Type::New(future_or_class, tav_function_legacy));
+  finalize_and_canonicalize(&future_or_function_type_legacy);
+  auto& future_or_function_type_nonnullable =
+      Type::Handle(Type::New(future_or_class, tav_function_nonnullable));
+  finalize_and_canonicalize(&future_or_function_type_nonnullable);
+
+  EXPECT_SUBTYPE(closure_type_nonnullable, future_or_function_type_nullable);
+  EXPECT_SUBTYPE(closure_type_nonnullable, future_or_function_type_legacy);
+  EXPECT_SUBTYPE(closure_type_nonnullable, future_or_function_type_nonnullable);
+  EXPECT_SUBTYPE(closure_type_legacy, future_or_function_type_nullable);
+  EXPECT_SUBTYPE(closure_type_legacy, future_or_function_type_legacy);
+  EXPECT_SUBTYPE(closure_type_legacy, future_or_function_type_nonnullable);
+  EXPECT_SUBTYPE(closure_type_nullable, future_or_function_type_nullable);
+  EXPECT_SUBTYPE(closure_type_nullable, future_or_function_type_legacy);
+  // Nullable types are not a subtype of non-nullable types in strict mode.
+  if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
+    EXPECT_NOT_SUBTYPE(closure_type_nullable,
+                       future_or_function_type_nonnullable);
+  } else {
+    EXPECT_SUBTYPE(closure_type_nullable, future_or_function_type_nonnullable);
+  }
+#undef EXPECT_NOT_SUBTYPE
+#undef EXPECT_SUBTYPE
 }
 
 TEST_CASE(Class_GetInstantiationOf) {
diff --git a/tools/VERSION b/tools/VERSION
index 445bc0f..dfd5a96 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 37
+PRERELEASE 38
 PRERELEASE_PATCH 0
\ No newline at end of file