Version 2.15.0-222.0.dev

Merge commit '938e7ae54e44f877042133258c58f4663512b6f8' into 'dev'
diff --git a/DEPS b/DEPS
index 3c180ca..6fb957c 100644
--- a/DEPS
+++ b/DEPS
@@ -46,7 +46,7 @@
   # has.
   "co19_rev": "cd6510c8346444792b37f6f26feffcc234a44dd1",
   # This line prevents conflicts when both packages are rolled simultaneously.
-  "co19_2_rev": "13344ad01472df9badfa78fd6aa411f042e13354",
+  "co19_2_rev": "a93f8484f7265f0849692d3b1208323393733f8d",
 
   # The internal benchmarks to use. See go/dart-benchmarks-internal
   "benchmarks_internal_rev": "076df10d9b77af337f2d8029725787155eb1cd52",
diff --git a/pkg/compiler/lib/src/js/size_estimator.dart b/pkg/compiler/lib/src/js/size_estimator.dart
index 022c4c2..33a45ed 100644
--- a/pkg/compiler/lib/src/js/size_estimator.dart
+++ b/pkg/compiler/lib/src/js/size_estimator.dart
@@ -843,6 +843,13 @@
     out("=>");
     int closingPosition;
     Node body = fun.body;
+    // Simplify arrow functions that return a single expression.
+    if (fun.implicitReturnAllowed && body is Block) {
+      final statement = unwrapBlockIfSingleStatement(body);
+      if (statement is Return) {
+        body = statement.value;
+      }
+    }
     if (body is Block) {
       closingPosition = blockOut(body);
     } else {
@@ -854,7 +861,7 @@
       visitNestedExpression(body, ASSIGNMENT,
           newInForInit: false, newAtStatementBegin: false);
       if (needsParens) out(")");
-      closingPosition = charCount - 1;
+      closingPosition = charCount;
     }
     return closingPosition;
   }
diff --git a/pkg/compiler/test/js/js_parser_test.dart b/pkg/compiler/test/js/js_parser_test.dart
index d338224..abbed83 100644
--- a/pkg/compiler/test/js/js_parser_test.dart
+++ b/pkg/compiler/test/js/js_parser_test.dart
@@ -18,6 +18,32 @@
   }
 }
 
+/// Tests an arrow expression with implicit returns allowed and disallowed.
+///
+/// Only checks the immediate, outermost arrow function.
+testArrowFunction(String arrowExpression,
+    [String implicitReturnExpect = "", String noImplicitReturnExpect = ""]) {
+  jsAst.ArrowFunction fun = js(arrowExpression);
+  jsAst.ArrowFunction implicitReturnFun = jsAst.ArrowFunction(
+      fun.params, fun.body,
+      asyncModifier: fun.asyncModifier, implicitReturnAllowed: true);
+  jsAst.ArrowFunction noImplicitReturnFun = jsAst.ArrowFunction(
+      fun.params, fun.body,
+      asyncModifier: fun.asyncModifier, implicitReturnAllowed: false);
+  String implicitReturnText =
+      jsAst.prettyPrint(implicitReturnFun, allowVariableMinification: false);
+  String noImplicitReturnText =
+      jsAst.prettyPrint(noImplicitReturnFun, allowVariableMinification: false);
+  String comparison =
+      implicitReturnExpect == "" ? arrowExpression : implicitReturnExpect;
+  Expect.stringEquals(comparison, implicitReturnText);
+  if (noImplicitReturnExpect == "") {
+    Expect.stringEquals(comparison, noImplicitReturnText);
+  } else {
+    Expect.stringEquals(noImplicitReturnExpect, noImplicitReturnText);
+  }
+}
+
 testError(String expression, [String expect = ""]) {
   bool doCheck(exception) {
     final exceptionText = '$exception';
@@ -193,13 +219,17 @@
   testExpression("a = b = c");
   testExpression("var a = b = c");
   // Arrow functions.
-  testExpression("(x) => x", "x => x");
-  testExpression("(x, y) => {\n  return x + y;\n}");
-  testExpression("() => 42");
-  testExpression('() => ({foo: "bar"})');
-  testExpression("() => {}", """
+  testArrowFunction("(x) => x", "x => x");
+  testArrowFunction(
+      "(x) => {\n  return x;\n}", "x => x", "x => {\n  return x;\n}");
+  testArrowFunction("(x, y) => {\n  return x + y;\n}", "(x, y) => x + y",
+      "(x, y) => {\n  return x + y;\n}");
+  testArrowFunction("() => 42");
+  testArrowFunction('() => ({foo: "bar"})');
+  testArrowFunction("() => {}", """
 () => {
 }""");
+  // Arrow function invocation.
   testExpression("(() => 1)()");
   testExpression("((x) => x)(y)", "(x => x)(y)");
   testExpression("(() => {x = 1;})()", """
diff --git a/pkg/js_ast/lib/src/nodes.dart b/pkg/js_ast/lib/src/nodes.dart
index 9ded615..b84ef51 100644
--- a/pkg/js_ast/lib/src/nodes.dart
+++ b/pkg/js_ast/lib/src/nodes.dart
@@ -1516,8 +1516,13 @@
   @override
   final AsyncModifier asyncModifier;
 
+  /// Indicates whether it is permissible to try to emit this arrow function
+  /// in a form with an implicit 'return'.
+  final bool implicitReturnAllowed;
+
   ArrowFunction(this.params, this.body,
-      {this.asyncModifier = AsyncModifier.sync});
+      {this.asyncModifier = AsyncModifier.sync,
+      this.implicitReturnAllowed = true});
 
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitArrowFunction(this);
 
@@ -1534,8 +1539,9 @@
     body.accept1(visitor, arg);
   }
 
-  ArrowFunction _clone() =>
-      ArrowFunction(params, body, asyncModifier: asyncModifier);
+  ArrowFunction _clone() => ArrowFunction(params, body,
+      asyncModifier: asyncModifier,
+      implicitReturnAllowed: implicitReturnAllowed);
 
   int get precedenceLevel => ASSIGNMENT;
 }
diff --git a/pkg/js_ast/lib/src/printer.dart b/pkg/js_ast/lib/src/printer.dart
index af0c66a..5c5620e 100644
--- a/pkg/js_ast/lib/src/printer.dart
+++ b/pkg/js_ast/lib/src/printer.dart
@@ -1128,6 +1128,15 @@
     spaceOut();
     int closingPosition;
     Node body = fun.body;
+    // Simplify arrow functions that return a single expression.
+    // Note that this can result in some sourcemapped positions disappearing
+    // around the elided Return. See http://dartbug.com/47354
+    if (fun.implicitReturnAllowed && body is Block) {
+      final statement = unwrapBlockIfSingleStatement(body);
+      if (statement is Return) {
+        body = statement.value;
+      }
+    }
     if (body is Block) {
       closingPosition =
           blockOut(body, shouldIndent: false, needsNewline: false);
@@ -1140,7 +1149,7 @@
       visitNestedExpression(body, ASSIGNMENT,
           newInForInit: false, newAtStatementBegin: false);
       if (needsParens) out(")");
-      closingPosition = _charCount - 1;
+      closingPosition = _charCount;
     }
     localNamer.leaveScope();
     return closingPosition;
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index 980f2e4..be2bfa6 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -365,51 +365,57 @@
 void Heap::NotifyIdle(int64_t deadline) {
   Thread* thread = Thread::Current();
   TIMELINE_FUNCTION_GC_DURATION(thread, "NotifyIdle");
-  GcSafepointOperationScope safepoint_operation(thread);
+  {
+    GcSafepointOperationScope safepoint_operation(thread);
 
-  // Check if we want to collect new-space first, because if we want to collect
-  // both new-space and old-space, the new-space collection should run first
-  // to shrink the root set (make old-space GC faster) and avoid
-  // intergenerational garbage (make old-space GC free more memory).
-  if (new_space_.ShouldPerformIdleScavenge(deadline)) {
-    CollectNewSpaceGarbage(thread, GCReason::kIdle);
+    // Check if we want to collect new-space first, because if we want to
+    // collect both new-space and old-space, the new-space collection should run
+    // first to shrink the root set (make old-space GC faster) and avoid
+    // intergenerational garbage (make old-space GC free more memory).
+    if (new_space_.ShouldPerformIdleScavenge(deadline)) {
+      CollectNewSpaceGarbage(thread, GCReason::kIdle);
+    }
+
+    // Check if we want to collect old-space, in decreasing order of cost.
+    // Because we use a deadline instead of a timeout, we automatically take any
+    // time used up by a scavenge into account when deciding if we can complete
+    // a mark-sweep on time.
+    if (old_space_.ShouldPerformIdleMarkCompact(deadline)) {
+      // We prefer mark-compact over other old space GCs if we have enough time,
+      // since it removes old space fragmentation and frees up most memory.
+      // Blocks for O(heap), roughtly twice as costly as mark-sweep.
+      CollectOldSpaceGarbage(thread, GCType::kMarkCompact, GCReason::kIdle);
+    } else if (old_space_.ReachedHardThreshold()) {
+      // Even though the following GC may exceed our idle deadline, we need to
+      // ensure than that promotions during idle scavenges do not lead to
+      // unbounded growth of old space. If a program is allocating only in new
+      // space and all scavenges happen during idle time, then NotifyIdle will
+      // be the only place that checks the old space allocation limit.
+      // Compare the tail end of Heap::CollectNewSpaceGarbage.
+      // Blocks for O(heap).
+      CollectOldSpaceGarbage(thread, GCType::kMarkSweep, GCReason::kIdle);
+    } else if (old_space_.ShouldStartIdleMarkSweep(deadline) ||
+               old_space_.ReachedSoftThreshold()) {
+      // If we have both work to do and enough time, start or finish GC.
+      // If we have crossed the soft threshold, ignore time; the next old-space
+      // allocation will trigger this work anyway, so we try to pay at least
+      // some of that cost with idle time.
+      // Blocks for O(roots).
+      PageSpace::Phase phase;
+      {
+        MonitorLocker ml(old_space_.tasks_lock());
+        phase = old_space_.phase();
+      }
+      if (phase == PageSpace::kAwaitingFinalization) {
+        CollectOldSpaceGarbage(thread, GCType::kMarkSweep, GCReason::kFinalize);
+      } else if (phase == PageSpace::kDone) {
+        StartConcurrentMarking(thread);
+      }
+    }
   }
 
-  // Check if we want to collect old-space, in decreasing order of cost.
-  // Because we use a deadline instead of a timeout, we automatically take any
-  // time used up by a scavenge into account when deciding if we can complete
-  // a mark-sweep on time.
-  if (old_space_.ShouldPerformIdleMarkCompact(deadline)) {
-    // We prefer mark-compact over other old space GCs if we have enough time,
-    // since it removes old space fragmentation and frees up most memory.
-    // Blocks for O(heap), roughtly twice as costly as mark-sweep.
-    CollectOldSpaceGarbage(thread, GCType::kMarkCompact, GCReason::kIdle);
-  } else if (old_space_.ReachedHardThreshold()) {
-    // Even though the following GC may exceed our idle deadline, we need to
-    // ensure than that promotions during idle scavenges do not lead to
-    // unbounded growth of old space. If a program is allocating only in new
-    // space and all scavenges happen during idle time, then NotifyIdle will be
-    // the only place that checks the old space allocation limit.
-    // Compare the tail end of Heap::CollectNewSpaceGarbage.
-    // Blocks for O(heap).
-    CollectOldSpaceGarbage(thread, GCType::kMarkSweep, GCReason::kIdle);
-  } else if (old_space_.ShouldStartIdleMarkSweep(deadline) ||
-             old_space_.ReachedSoftThreshold()) {
-    // If we have both work to do and enough time, start or finish GC.
-    // If we have crossed the soft threshold, ignore time; the next old-space
-    // allocation will trigger this work anyway, so we try to pay at least some
-    // of that cost with idle time.
-    // Blocks for O(roots).
-    PageSpace::Phase phase;
-    {
-      MonitorLocker ml(old_space_.tasks_lock());
-      phase = old_space_.phase();
-    }
-    if (phase == PageSpace::kAwaitingFinalization) {
-      CollectOldSpaceGarbage(thread, GCType::kMarkSweep, GCReason::kFinalize);
-    } else if (phase == PageSpace::kDone) {
-      StartConcurrentMarking(thread);
-    }
+  if (OS::GetCurrentMonotonicMicros() < deadline) {
+    SemiSpace::DrainCache();
   }
 }
 
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index 5b236c2..8897dda 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -651,15 +651,17 @@
   page_cache_mutex = new Mutex(NOT_IN_PRODUCT("page_cache_mutex"));
 }
 
-void SemiSpace::Cleanup() {
-  {
-    MutexLocker ml(page_cache_mutex);
-    ASSERT(page_cache_size >= 0);
-    ASSERT(page_cache_size <= kPageCacheCapacity);
-    while (page_cache_size > 0) {
-      delete page_cache[--page_cache_size];
-    }
+void SemiSpace::DrainCache() {
+  MutexLocker ml(page_cache_mutex);
+  ASSERT(page_cache_size >= 0);
+  ASSERT(page_cache_size <= kPageCacheCapacity);
+  while (page_cache_size > 0) {
+    delete page_cache[--page_cache_size];
   }
+}
+
+void SemiSpace::Cleanup() {
+  DrainCache();
   delete page_cache_mutex;
   page_cache_mutex = nullptr;
 }
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index 414b62e..f538f3a 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -168,6 +168,7 @@
 class SemiSpace {
  public:
   static void Init();
+  static void DrainCache();
   static void Cleanup();
   static intptr_t CachedSize();
 
diff --git a/tools/FAKE_COMMITS b/tools/FAKE_COMMITS
index 1599948..b709239 100644
--- a/tools/FAKE_COMMITS
+++ b/tools/FAKE_COMMITS
@@ -4,6 +4,7 @@
 
 File for landing text only commits in the dart repo.
 
+Test
 Tracer update
 Force build after chrome-bot messup
 Testing svn server
diff --git a/tools/VERSION b/tools/VERSION
index 80892437..87e5deb 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 221
+PRERELEASE 222
 PRERELEASE_PATCH 0
\ No newline at end of file