Version 2.17.0-11.0.dev

Merge commit '0e5a539315e690eb1bfac9ecf8a676822fcbf714' into 'dev'
diff --git a/pkg/vm/lib/transformations/to_string_transformer.dart b/pkg/vm/lib/transformations/to_string_transformer.dart
index 0cd1b5f..824282d 100644
--- a/pkg/vm/lib/transformations/to_string_transformer.dart
+++ b/pkg/vm/lib/transformations/to_string_transformer.dart
@@ -19,6 +19,8 @@
   /// 'package:flutter/foundation.dart'.
   final Set<String> _packageUris;
 
+  final Map<Class, bool> _inheritedKeepAnnotations = {};
+
   /// Turn 'dart:ui' into 'dart:ui', or
   /// 'package:flutter/src/semantics_event.dart' into 'package:flutter'.
   String _importUriToPackage(Uri importUri) =>
@@ -29,7 +31,18 @@
         .contains(_importUriToPackage(node.enclosingLibrary.importUri));
   }
 
-  bool _hasKeepAnnotation(Procedure node) {
+  bool _hasKeepAnnotation(Procedure node) =>
+      _hasPragma(node, 'flutter:keep-to-string');
+
+  bool _hasKeepAnnotationOnClass(Class node) =>
+      _hasPragma(node, 'flutter:keep-to-string-in-subtypes');
+
+  bool _hasInheritedKeepAnnotation(Class node) =>
+      _inheritedKeepAnnotations[node] ??= (_hasKeepAnnotationOnClass(node) ||
+          node.supers
+              .any((Supertype t) => _hasInheritedKeepAnnotation(t.classNode)));
+
+  bool _hasPragma(Annotatable node, String pragma) {
     for (ConstantExpression expression
         in node.annotations.whereType<ConstantExpression>()) {
       if (expression.constant is! InstanceConstant) {
@@ -43,8 +56,7 @@
         for (var fieldRef in constant.fieldValues.keys) {
           if (fieldRef.asField.name.text == 'name') {
             Constant? name = constant.fieldValues[fieldRef];
-            return name is StringConstant &&
-                name.value == 'flutter:keep-to-string';
+            return name is StringConstant && name.value == pragma;
           }
         }
         return false;
@@ -61,7 +73,8 @@
         !node.isAbstract &&
         !node.enclosingClass!.isEnum &&
         _isInTargetPackage(node) &&
-        !_hasKeepAnnotation(node)) {
+        !_hasKeepAnnotation(node) &&
+        !_hasInheritedKeepAnnotation(node.enclosingClass!)) {
       node.function.body!.replaceWith(
         ReturnStatement(
           SuperMethodInvocation(
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart b/pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart
index 96544f2..aa53235 100644
--- a/pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart
+++ b/pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart
@@ -5,6 +5,7 @@
 import 'dart:convert';
 
 const keepToString = pragma('flutter:keep-to-string');
+const keepToStringInSubtypes = pragma('flutter:keep-to-string-in-subtypes');
 
 String toString() => 'I am static';
 
@@ -26,9 +27,28 @@
   String toString() => 'I am a Keep';
 }
 
+@keepToStringInSubtypes
+class Base1 {}
+
+class Base2 extends Base1 {}
+
+class Base3 extends Object with Base2 {}
+
+class KeepInherited implements Base3 {
+  @override
+  String toString() => 'Heir';
+}
+
+class MyException implements Exception {
+  @override
+  String toString() => 'A very detailed message';
+}
+
 void main() {
   final IFoo foo = Foo();
   print(foo.toString());
   print(Keep().toString());
   print(FooEnum.B.toString());
+  print(KeepInherited().toString());
+  print(MyException().toString());
 }
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect b/pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect
index 29c959a..6d48b41 100644
--- a/pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect
+++ b/pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect
@@ -39,7 +39,45 @@
   method toString() → core::String
     return "I am a Keep";
 }
+@#C16
+class Base1 extends core::Object {
+  synthetic constructor •() → self::Base1
+    : super core::Object::•()
+    ;
+}
+class Base2 extends self::Base1 {
+  synthetic constructor •() → self::Base2
+    : super self::Base1::•()
+    ;
+}
+abstract class _Base3&Object&Base2 extends core::Object implements self::Base2 /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/  {
+  const synthetic constructor •() → self::_Base3&Object&Base2
+    : super core::Object::•()
+    ;
+}
+class Base3 extends self::_Base3&Object&Base2 {
+  synthetic constructor •() → self::Base3
+    : super self::_Base3&Object&Base2::•()
+    ;
+}
+class KeepInherited extends core::Object implements self::Base3 {
+  synthetic constructor •() → self::KeepInherited
+    : super core::Object::•()
+    ;
+  @#C1
+  method toString() → core::String
+    return "Heir";
+}
+class MyException extends core::Object implements core::Exception {
+  synthetic constructor •() → self::MyException
+    : super core::Object::•()
+    ;
+  @#C1
+  method toString() → core::String
+    return "A very detailed message";
+}
 static const field core::pragma keepToString = #C14;
+static const field core::pragma keepToStringInSubtypes = #C16;
 static method toString() → core::String
   return "I am static";
 static method main() → void {
@@ -47,6 +85,8 @@
   core::print(foo.{self::IFoo::toString}(){() → core::String});
   core::print(new self::Keep::•().{self::Keep::toString}(){() → core::String});
   core::print(#C7.{self::FooEnum::toString}(){() → core::String});
+  core::print(new self::KeepInherited::•().{self::KeepInherited::toString}(){() → core::String});
+  core::print(new self::MyException::•().{self::MyException::toString}(){() → core::String});
 }
 constants  {
   #C1 = core::_Override {}
@@ -63,4 +103,6 @@
   #C12 = "flutter:keep-to-string"
   #C13 = null
   #C14 = core::pragma {name:#C12, options:#C13}
+  #C15 = "flutter:keep-to-string-in-subtypes"
+  #C16 = core::pragma {name:#C15, options:#C13}
 }
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/transformed.expect b/pkg/vm/testcases/transformations/to_string_transformer/transformed.expect
index dca5efb..75d87ae 100644
--- a/pkg/vm/testcases/transformations/to_string_transformer/transformed.expect
+++ b/pkg/vm/testcases/transformations/to_string_transformer/transformed.expect
@@ -39,7 +39,45 @@
   method toString() → core::String
     return "I am a Keep";
 }
+@#C16
+class Base1 extends core::Object {
+  synthetic constructor •() → self::Base1
+    : super core::Object::•()
+    ;
+}
+class Base2 extends self::Base1 {
+  synthetic constructor •() → self::Base2
+    : super self::Base1::•()
+    ;
+}
+abstract class _Base3&Object&Base2 extends core::Object implements self::Base2 /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/  {
+  const synthetic constructor •() → self::_Base3&Object&Base2
+    : super core::Object::•()
+    ;
+}
+class Base3 extends self::_Base3&Object&Base2 {
+  synthetic constructor •() → self::Base3
+    : super self::_Base3&Object&Base2::•()
+    ;
+}
+class KeepInherited extends core::Object implements self::Base3 {
+  synthetic constructor •() → self::KeepInherited
+    : super core::Object::•()
+    ;
+  @#C1
+  method toString() → core::String
+    return "Heir";
+}
+class MyException extends core::Object implements core::Exception {
+  synthetic constructor •() → self::MyException
+    : super core::Object::•()
+    ;
+  @#C1
+  method toString() → core::String
+    return "A very detailed message";
+}
 static const field core::pragma keepToString = #C14;
+static const field core::pragma keepToStringInSubtypes = #C16;
 static method toString() → core::String
   return "I am static";
 static method main() → void {
@@ -47,6 +85,8 @@
   core::print(foo.{self::IFoo::toString}(){() → core::String});
   core::print(new self::Keep::•().{self::Keep::toString}(){() → core::String});
   core::print(#C7.{self::FooEnum::toString}(){() → core::String});
+  core::print(new self::KeepInherited::•().{self::KeepInherited::toString}(){() → core::String});
+  core::print(new self::MyException::•().{self::MyException::toString}(){() → core::String});
 }
 constants  {
   #C1 = core::_Override {}
@@ -63,4 +103,6 @@
   #C12 = "flutter:keep-to-string"
   #C13 = null
   #C14 = core::pragma {name:#C12, options:#C13}
+  #C15 = "flutter:keep-to-string-in-subtypes"
+  #C16 = core::pragma {name:#C15, options:#C13}
 }
diff --git a/runtime/docs/pragmas.md b/runtime/docs/pragmas.md
index 589699c..77d6d8e 100644
--- a/runtime/docs/pragmas.md
+++ b/runtime/docs/pragmas.md
@@ -41,3 +41,14 @@
 | Pragma | Meaning |
 | --- | --- |
 | `vm:testing.unsafe.trace-entrypoints-fn` | [Observing which flow-graph-level entry-point was used when a function was called](compiler/frontend/testing_trace_entrypoints_pragma.md) |
+
+## Flutter toString transformer pragmas
+
+These pragmas are useful to exclude certain toString methods from toString transformation,
+which is enabled with `--delete-tostring-package-uri` option in kernel compilers and
+used by Flutter to remove certain toString methods in release mode to reduce size.
+
+| Pragma | Meaning |
+| --- | --- |
+| `flutter:keep-to-string` | Avoid transforming the annotated toString method. |
+| `flutter:keep-to-string-in-subtypes` | Avoid transforming toString methods in all subtypes of the annotated class. |
diff --git a/sdk/lib/core/errors.dart b/sdk/lib/core/errors.dart
index 6190c83..00d69ce 100644
--- a/sdk/lib/core/errors.dart
+++ b/sdk/lib/core/errors.dart
@@ -64,6 +64,7 @@
 /// For example, the [String.contains] method will use a [RangeError]
 /// if its `startIndex` isn't in the range `0..length`,
 /// which is easily created by `RangeError.range(startIndex, 0, length)`.
+@pragma('flutter:keep-to-string-in-subtypes')
 class Error {
   Error(); // Prevent use as mixin.
 
diff --git a/sdk/lib/core/exceptions.dart b/sdk/lib/core/exceptions.dart
index 889c991..f522d27 100644
--- a/sdk/lib/core/exceptions.dart
+++ b/sdk/lib/core/exceptions.dart
@@ -16,6 +16,7 @@
 /// is discouraged in library code since it doesn't give users a precise
 /// type they can catch. It may be reasonable to use instances of this
 /// class in tests or during development.
+@pragma('flutter:keep-to-string-in-subtypes')
 abstract class Exception {
   factory Exception([var message]) => _Exception(message);
 }
diff --git a/tools/VERSION b/tools/VERSION
index 5ade2e0..de7cb11 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 10
+PRERELEASE 11
 PRERELEASE_PATCH 0
\ No newline at end of file