From f3187885c2917ef90017c0624d4e09aaa19adc2a Mon Sep 17 00:00:00 2001 From: George Steel Date: Mon, 13 Jun 2022 12:24:11 +0100 Subject: [PATCH 1/3] Psalm can not always infer the return type in dependents Signed-off-by: George Steel --- src/ServiceManager.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ServiceManager.php b/src/ServiceManager.php index 6c228fc3..c87879c1 100644 --- a/src/ServiceManager.php +++ b/src/ServiceManager.php @@ -259,6 +259,9 @@ public function build($name, ?array $options = null) /** * {@inheritDoc} + * + * @param string|class-string $name + * @return bool */ public function has($name) { From c9742ffe8195ec22e622f0774ce7aa71ebd52a28 Mon Sep 17 00:00:00 2001 From: George Steel Date: Mon, 13 Jun 2022 12:46:38 +0100 Subject: [PATCH 2/3] Adds psalm assertion to plugin manager interface `validate()` Signed-off-by: George Steel --- src/PluginManagerInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PluginManagerInterface.php b/src/PluginManagerInterface.php index 1daa4665..156da327 100644 --- a/src/PluginManagerInterface.php +++ b/src/PluginManagerInterface.php @@ -11,6 +11,8 @@ * Interface for a plugin manager * * A plugin manager is a specialized service locator used to create homogeneous objects + * + * @template InstanceType of object */ interface PluginManagerInterface extends ServiceLocatorInterface { @@ -22,6 +24,7 @@ interface PluginManagerInterface extends ServiceLocatorInterface * @throws InvalidServiceException If created instance does not respect the * constraint on type imposed by the plugin manager. * @throws ContainerExceptionInterface If any other error occurs. + * @psalm-assert InstanceType $instance */ public function validate($instance); } From f1499ed657e37488eb3e800b0596afb2925459c4 Mon Sep 17 00:00:00 2001 From: George Steel Date: Mon, 13 Jun 2022 12:56:10 +0100 Subject: [PATCH 3/3] Improve type inference in dependent projects Signed-off-by: George Steel --- psalm-baseline.xml | 7 ++----- src/AbstractPluginManager.php | 14 +++++++++++--- src/ServiceLocatorInterface.php | 4 +++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 3567d9d1..ffddff3b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + new $requestedName(...$arguments) @@ -405,7 +405,7 @@ $names[$name] $object[$shared ? $method : 'build'] - + $callSequence $first $idx1 @@ -502,9 +502,6 @@ $autoload - - $instance - assertIsArray assertIsArray diff --git a/src/AbstractPluginManager.php b/src/AbstractPluginManager.php index 21128a98..8b5569a5 100644 --- a/src/AbstractPluginManager.php +++ b/src/AbstractPluginManager.php @@ -32,6 +32,8 @@ * The implementation extends `ServiceManager`, thus providing the same set * of capabilities as found in that implementation. * + * @template InstanceType of object + * @implements PluginManagerInterface * @psalm-import-type ServiceManagerConfiguration from ServiceManager * @psalm-suppress PropertyNotSetInConstructor */ @@ -48,7 +50,7 @@ abstract class AbstractPluginManager extends ServiceManager implements PluginMan * An object type that the created instance must be instanced of * * @var null|string - * @psalm-var null|class-string + * @psalm-var null|class-string */ protected $instanceOf; @@ -131,6 +133,10 @@ public function configure(array $config) * Override setService for additional plugin validation. * * {@inheritDoc} + * + * @param string|class-string $name + * @param InstanceType $service + * @psalm-suppress MoreSpecificImplementedParamType */ public function setService($name, $service) { @@ -139,9 +145,10 @@ public function setService($name, $service) } /** - * @param string $name Service name of plugin to retrieve. + * @param class-string|string $name Service name of plugin to retrieve. * @param null|array $options Options to use when creating the instance. * @return mixed + * @psalm-return ($name is class-string ? InstanceType : mixed) * @throws Exception\ServiceNotFoundException If the manager does not have * a service definition for the instance, and the service is not * auto-invokable. @@ -162,7 +169,6 @@ public function get($name, ?array $options = null) $this->setFactory($name, Factory\InvokableFactory::class); } - /** @psalm-suppress MixedAssignment */ $instance = ! $options ? parent::get($name) : $this->build($name, $options); $this->validate($instance); return $instance; @@ -170,6 +176,8 @@ public function get($name, ?array $options = null) /** * {@inheritDoc} + * + * @psalm-assert InstanceType $instance */ public function validate($instance) { diff --git a/src/ServiceLocatorInterface.php b/src/ServiceLocatorInterface.php index 44272f3e..a0dc4600 100644 --- a/src/ServiceLocatorInterface.php +++ b/src/ServiceLocatorInterface.php @@ -15,9 +15,11 @@ interface ServiceLocatorInterface extends ContainerInterface /** * Build a service by its name, using optional options (such services are NEVER cached). * - * @param string $name + * @template T of object + * @param string|class-string $name * @param null|array $options * @return mixed + * @psalm-return ($name is class-string ? T : mixed) * @throws Exception\ServiceNotFoundException If no factory/abstract * factory could be found to create the instance. * @throws Exception\ServiceNotCreatedException If factory/delegator fails