[plugin_platform_interface] Switch `PlatformInterface._instanceToken` to an expando. (#6411)

This change replaces `PlatformInterface._instanceToken` (an instance
field that points from a `PlatformInterface` to its corresponding
token) with an expando that maps from `PlatformInterface` to `Object`.
There is no change to the public API, and no change to the behavior of
users' production code.

This change ensures that if a customer tries to implement
`PlatformInterface` using `implements` rather than `extends`, the code
in `PlatformInterface._verify` won't try to access the private
`_instanceToken` field in `PlatformInterface`.  This is important
because an upcoming change to the dart language is going to cause such
accesses to throw exceptions rather than deferring to `noSuchMethod`
(see https://github.com/dart-lang/language/issues/2020 for details).

Fixes https://github.com/flutter/flutter/issues/109339.
diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md
index 0e9b701..0b5a6b6 100644
--- a/packages/plugin_platform_interface/CHANGELOG.md
+++ b/packages/plugin_platform_interface/CHANGELOG.md
@@ -1,7 +1,11 @@
-## NEXT
+## 2.1.3
 
 * Minor fixes for new analysis options.
 * Adds additional tests for `PlatformInterface` and `MockPlatformInterfaceMixin`.
+* Modifies `PlatformInterface` to use an expando for detecting if a customer
+  tries to implement PlatformInterface using `implements` rather than `extends`.
+  This ensures that `verify` will continue to work as advertized after
+  https://github.com/dart-lang/language/issues/2020 is implemented.
 
 ## 2.1.2
 
diff --git a/packages/plugin_platform_interface/lib/plugin_platform_interface.dart b/packages/plugin_platform_interface/lib/plugin_platform_interface.dart
index a03c9ce..6733b29 100644
--- a/packages/plugin_platform_interface/lib/plugin_platform_interface.dart
+++ b/packages/plugin_platform_interface/lib/plugin_platform_interface.dart
@@ -44,9 +44,20 @@
   /// derived classes.
   ///
   /// @param token The same, non-`const` `Object` that will be passed to `verify`.
-  PlatformInterface({required Object token}) : _instanceToken = token;
+  PlatformInterface({required Object token}) {
+    _instanceTokens[this] = token;
+  }
 
-  final Object? _instanceToken;
+  /// Expando mapping instances of PlatformInterface to their associated tokens.
+  /// The reason this is not simply a private field of type `Object?` is because
+  /// as of the implementation of field promotion in Dart
+  /// (https://github.com/dart-lang/language/issues/2020), it is a runtime error
+  /// to invoke a private member that is mocked in another library.  The expando
+  /// approach prevents [_verify] from triggering this runtime exception when
+  /// encountering an implementation that uses `implements` rather than
+  /// `extends`.  This in turn allows [_verify] to throw an [AssertionError] (as
+  /// documented).
+  static final Expando<Object> _instanceTokens = Expando<Object>();
 
   /// Ensures that the platform instance was constructed with a non-`const` token
   /// that matches the provided token and throws [AssertionError] if not.
@@ -89,10 +100,10 @@
       return;
     }
     if (preventConstObject &&
-        identical(instance._instanceToken, const Object())) {
+        identical(_instanceTokens[instance], const Object())) {
       throw AssertionError('`const Object()` cannot be used as the token.');
     }
-    if (!identical(token, instance._instanceToken)) {
+    if (!identical(token, _instanceTokens[instance])) {
       throw AssertionError(
           'Platform interfaces must not be implemented with `implements`');
     }
diff --git a/packages/plugin_platform_interface/pubspec.yaml b/packages/plugin_platform_interface/pubspec.yaml
index f4800d4..6a4bc48 100644
--- a/packages/plugin_platform_interface/pubspec.yaml
+++ b/packages/plugin_platform_interface/pubspec.yaml
@@ -15,7 +15,7 @@
 # be done when absolutely necessary and after the ecosystem has already migrated to 2.X.Y version
 # that is forward compatible with 3.0.0 (ideally the ecosystem have migrated to depend on:
 # `plugin_platform_interface: >=2.X.Y <4.0.0`).
-version: 2.1.2
+version: 2.1.3
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart b/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart
index 329cecb..869017c 100644
--- a/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart
+++ b/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart
@@ -21,6 +21,12 @@
 class ImplementsSamplePluginPlatform extends Mock
     implements SamplePluginPlatform {}
 
+class ImplementsSamplePluginPlatformUsingNoSuchMethod
+    implements SamplePluginPlatform {
+  @override
+  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
 class ImplementsSamplePluginPlatformUsingMockPlatformInterfaceMixin extends Mock
     with MockPlatformInterfaceMixin
     implements SamplePluginPlatform {}
@@ -98,6 +104,13 @@
       }, throwsA(isA<AssertionError>()));
     });
 
+    test('prevents implmentation with `implements` and `noSuchMethod`', () {
+      expect(() {
+        SamplePluginPlatform.instance =
+            ImplementsSamplePluginPlatformUsingNoSuchMethod();
+      }, throwsA(isA<AssertionError>()));
+    });
+
     test('allows mocking with `implements`', () {
       final SamplePluginPlatform mock =
           ImplementsSamplePluginPlatformUsingMockPlatformInterfaceMixin();