diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md index 0e9b701444fd..0b5a6b63a52f 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 a03c9ce2d367..6733b29953b0 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 @@ abstract class PlatformInterface { /// 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 _instanceTokens = Expando(); /// 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 @@ abstract class PlatformInterface { 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 f4800d444e66..6a4bc488693b 100644 --- a/packages/plugin_platform_interface/pubspec.yaml +++ b/packages/plugin_platform_interface/pubspec.yaml @@ -15,7 +15,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ # 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 329cecb16091..869017cd4f23 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 SamplePluginPlatform extends PlatformInterface { 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 @@ void main() { }, throwsA(isA())); }); + test('prevents implmentation with `implements` and `noSuchMethod`', () { + expect(() { + SamplePluginPlatform.instance = + ImplementsSamplePluginPlatformUsingNoSuchMethod(); + }, throwsA(isA())); + }); + test('allows mocking with `implements`', () { final SamplePluginPlatform mock = ImplementsSamplePluginPlatformUsingMockPlatformInterfaceMixin();