Version 2.15.0-161.0.dev

Merge commit 'c74dd07a4a3bc139c6a3b6e21eee79ac4a6440dd' into 'dev'
diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h
index 862b338..3ed3608 100644
--- a/runtime/vm/compiler/recognized_methods_list.h
+++ b/runtime/vm/compiler/recognized_methods_list.h
@@ -81,7 +81,7 @@
   V(::, _toClampedUint8, ConvertIntToClampedUint8, 0x00fc4650)                 \
   V(::, copyRangeFromUint8ListToOneByteString,                                 \
     CopyRangeFromUint8ListToOneByteString, 0x0df019c5)                         \
-  V(_StringBase, _interpolate, StringBaseInterpolate, 0xea1eafca)              \
+  V(_StringBase, _interpolate, StringBaseInterpolate, 0xfc28bc84)              \
   V(_IntegerImplementation, toDouble, IntegerToDouble, 0x97728b46)             \
   V(_Double, _add, DoubleAdd, 0xea666327)                                      \
   V(_Double, _sub, DoubleSub, 0x28474c2e)                                      \
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
index 33854c7..89c90d5 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
@@ -717,7 +717,11 @@
 String str(obj) {
   if (obj == null) return "null";
   if (obj is String) return obj;
-  return _notNull(JS('!', '#[#]()', obj, extensionSymbol('toString')));
+  final result = JS('', '#[#]()', obj, extensionSymbol('toString'));
+  // TODO(40614): Declare `result` as String once non-nullability is sound.
+  if (result is String) return result;
+  // Since Dart 2.0, `null` is the only other option.
+  throw ArgumentError.value(obj, 'object', "toString method returned 'null'");
 }
 
 // TODO(jmesserly): is the argument type verified statically?
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index b39faed..ff38ff8 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -131,9 +131,12 @@
   } else if (value == null) {
     return 'null';
   }
-  var res = value.toString();
-  if (res is! String) throw argumentErrorValue(value);
-  return res;
+  var result = value.toString();
+  if (result is! String) {
+    throw ArgumentError.value(
+        value, 'object', "toString method returned 'null'");
+  }
+  return result;
 }
 
 // Called from generated code.
diff --git a/sdk/lib/_internal/vm/lib/string_buffer_patch.dart b/sdk/lib/_internal/vm/lib/string_buffer_patch.dart
index 3991deb..8a30546 100644
--- a/sdk/lib/_internal/vm/lib/string_buffer_patch.dart
+++ b/sdk/lib/_internal/vm/lib/string_buffer_patch.dart
@@ -61,7 +61,7 @@
 
   @patch
   void write(Object? obj) {
-    String str = obj.toString();
+    String str = "$obj";
     if (str.isEmpty) return;
     _consumeBuffer();
     _addPart(str);
diff --git a/sdk/lib/_internal/vm/lib/string_patch.dart b/sdk/lib/_internal/vm/lib/string_patch.dart
index ff41360..66e3dcd 100644
--- a/sdk/lib/_internal/vm/lib/string_patch.dart
+++ b/sdk/lib/_internal/vm/lib/string_patch.dart
@@ -829,8 +829,9 @@
   static String _interpolateSingle(Object? o) {
     if (o is String) return o;
     final s = o.toString();
+    // TODO(40614): Remove once non-nullability is sound.
     if (s is! String) {
-      throw new ArgumentError(s);
+      throw _interpolationError(o, s);
     }
     return s;
   }
@@ -855,15 +856,17 @@
         totalLength += s.length;
         i++;
       } else if (s is! String) {
-        throw new ArgumentError(s);
+        // TODO(40614): Remove once non-nullability is sound.
+        throw _interpolationError(e, s);
       } else {
         // Handle remaining elements without checking for one-byte-ness.
         while (++i < numValues) {
           final e = values[i];
           final s = e.toString();
           values[i] = s;
+          // TODO(40614): Remove once non-nullability is sound.
           if (s is! String) {
-            throw new ArgumentError(s);
+            throw _interpolationError(e, s);
           }
         }
         return _concatRangeNative(values, 0, numValues);
@@ -873,6 +876,12 @@
     return _OneByteString._concatAll(values, totalLength);
   }
 
+  static ArgumentError _interpolationError(Object? o, Object? result) {
+    // Since Dart 2.0, [result] can only be null.
+    return new ArgumentError.value(
+        o, "object", "toString method returned 'null'");
+  }
+
   Iterable<Match> allMatches(String string, [int start = 0]) {
     if (start < 0 || start > string.length) {
       throw new RangeError.range(start, 0, string.length, "start");
diff --git a/sdk/lib/core/print.dart b/sdk/lib/core/print.dart
index 460a852..10a0263 100644
--- a/sdk/lib/core/print.dart
+++ b/sdk/lib/core/print.dart
@@ -6,7 +6,7 @@
 
 /// Prints a string representation of the object to the console.
 void print(Object? object) {
-  String line = checkNotNullable(object.toString(), "object.toString()");
+  String line = "$object";
   var toZone = printToZone;
   if (toZone == null) {
     printToConsole(line);
diff --git a/tests/corelib_2/string_interpolation_error_test.dart b/tests/corelib_2/string_interpolation_error_test.dart
new file mode 100644
index 0000000..b9764e6
--- /dev/null
+++ b/tests/corelib_2/string_interpolation_error_test.dart
@@ -0,0 +1,30 @@
+// 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.
+
+// @dart = 2.9
+
+import "package:expect/expect.dart";
+
+class BadToString {
+  @override
+  String toString() => null;
+}
+
+void test(expected, object) {
+  var message = '';
+  if (expected == null) {
+    Expect.throws(() => '$object',
+        (error) => '$error'.contains("toString method returned 'null'"));
+  } else {
+    Expect.equals(expected, '$object');
+  }
+}
+
+void main() {
+  test("123", 123);
+  test("null", null);
+  test(null, BadToString());
+  test(null, [BadToString()]);
+  test(null, {BadToString()});
+}
diff --git a/tools/VERSION b/tools/VERSION
index ba9cb17..23ba9f3 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 160
+PRERELEASE 161
 PRERELEASE_PATCH 0
\ No newline at end of file