[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();