From f69348a23f014f5d9ceaa44ed71368fab16eb470 Mon Sep 17 00:00:00 2001 From: Andrew Gilbert Date: Mon, 10 Feb 2020 12:53:39 -0700 Subject: [PATCH 1/8] Be more specific about where to suppress `InvalidCast` --- src/Enum.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Enum.php b/src/Enum.php index 5c15251..c07454b 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -42,7 +42,6 @@ abstract class Enum implements \JsonSerializable * @param mixed $value * * @psalm-param T $value - * @psalm-suppress InvalidCast * @throws \UnexpectedValueException if incompatible type is given. */ public function __construct($value) @@ -52,6 +51,7 @@ public function __construct($value) } if (!$this->isValid($value)) { + /** @psalm-suppress InvalidCast */ throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); } From 396357fbc4b25c131ff85ca5fb3e42063e96b871 Mon Sep 17 00:00:00 2001 From: Andrew Gilbert Date: Mon, 10 Feb 2020 12:56:20 -0700 Subject: [PATCH 2/8] Mark static methods as `external-mutation-free` They all modify the cache, so they can't be `mutation-free`, and they rely on the object state, so they're not `pure`. --- src/Enum.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Enum.php b/src/Enum.php index c07454b..39796eb 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -70,7 +70,7 @@ public function getValue() /** * Returns the enum key (i.e. the constant name). * - * @psalm-pure + * @psalm-external-mutation-free * @return mixed */ public function getKey() @@ -106,6 +106,7 @@ final public function equals($variable = null): bool /** * Returns the names (keys) of all constants in the Enum class * + * @psalm-external-mutation-free * @return array */ public static function keys() @@ -116,6 +117,7 @@ public static function keys() /** * Returns instances of the Enum class of all Enum constants * + * @psalm-external-mutation-free * @return static[] Constant name in key, Enum instance in value */ public static function values() @@ -132,7 +134,7 @@ public static function values() /** * Returns all possible values as an array * - * @psalm-pure + * @psalm-external-mutation-free * @psalm-return array * @return array Constant name in key, constant value in value */ @@ -153,7 +155,7 @@ public static function toArray() * * @param $value * @psalm-param mixed $value - * + * @psalm-external-mutation-free * @return bool */ public static function isValid($value) @@ -166,7 +168,7 @@ public static function isValid($value) * * @param $key * @psalm-param string $key - * + * @psalm-external-mutation-free * @return bool */ public static function isValidKey($key) @@ -182,7 +184,7 @@ public static function isValidKey($key) * @param $value * * @psalm-param mixed $value - * @psalm-pure + * @psalm-external-mutation-free * @return mixed */ public static function search($value) @@ -197,6 +199,7 @@ public static function search($value) * @param array $arguments * * @return static + * @psalm-external-mutation-free * @throws \BadMethodCallException */ public static function __callStatic($name, $arguments) From e58f812b736b4dcc0c0c279b89ad9a4b60158f64 Mon Sep 17 00:00:00 2001 From: Andrew Gilbert Date: Mon, 10 Feb 2020 12:57:21 -0700 Subject: [PATCH 3/8] Explain construction typing to Psalm This makes it so Psalm doesn't think we're passing `mixed` values around. We're either constructing using an instance of ourselves, or using a value which we're going to wrap. --- src/Enum.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Enum.php b/src/Enum.php index 39796eb..6605538 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -41,12 +41,13 @@ abstract class Enum implements \JsonSerializable * * @param mixed $value * - * @psalm-param T $value + * @psalm-param static|T $value * @throws \UnexpectedValueException if incompatible type is given. */ public function __construct($value) { if ($value instanceof static) { + /** @psalm-var T */ $value = $value->getValue(); } @@ -55,6 +56,7 @@ public function __construct($value) throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); } + /** @psalm-var T */ $this->value = $value; } From 967787b5d216f480f1fea1ab7f3e519b8bfcbcd9 Mon Sep 17 00:00:00 2001 From: Andrew Gilbert Date: Mon, 10 Feb 2020 12:58:20 -0700 Subject: [PATCH 4/8] Add more-specific Psalm return types Might as well be more precise about this, as Psalm supports doing so. --- src/Enum.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Enum.php b/src/Enum.php index 6605538..1dec57e 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -109,6 +109,7 @@ final public function equals($variable = null): bool * Returns the names (keys) of all constants in the Enum class * * @psalm-external-mutation-free + * @psalm-return list * @return array */ public static function keys() @@ -120,6 +121,7 @@ public static function keys() * Returns instances of the Enum class of all Enum constants * * @psalm-external-mutation-free + * @psalm-return array * @return static[] Constant name in key, Enum instance in value */ public static function values() From 27aa6b1c43cb0898f1582fc07ff673fc20e953c7 Mon Sep 17 00:00:00 2001 From: Andrew Gilbert Date: Mon, 10 Feb 2020 12:58:49 -0700 Subject: [PATCH 5/8] Explain loop typing to Psalm This makes it clear that we're not passing `mixed` values around. --- src/Enum.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Enum.php b/src/Enum.php index 1dec57e..9f5e974 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -128,6 +128,7 @@ public static function values() { $values = array(); + /** @psalm-var T $value */ foreach (static::toArray() as $key => $value) { $values[$key] = new static($value); } From 3d5c198dc031d468d865e9c5695f7cb28aa3ab91 Mon Sep 17 00:00:00 2001 From: Jarred Stelfox Date: Wed, 12 Feb 2020 09:31:15 -0800 Subject: [PATCH 6/8] Mark all methods as pure This means every function can be called from an immutable / pure context Note: ToArray will fail psalm on this commit as (rightfully) there is an impure psalm issue This commit is a bit of a lie to psalm: Psalm is correct that our object is not actually immutable / pure. But is if you remove caching. This lie seems tolerable (despite how bad a practice overriding the typechecker is) because the mutation is entirely memoization. --- src/Enum.php | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Enum.php b/src/Enum.php index 9f5e974..b502c34 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -31,6 +31,8 @@ abstract class Enum implements \JsonSerializable /** * Store existing constants in a static cache per object. * + * @psalm-pure + * * @var array * @psalm-var array> */ @@ -39,6 +41,7 @@ abstract class Enum implements \JsonSerializable /** * Creates a new value of some type * + * @psalm-pure * @param mixed $value * * @psalm-param static|T $value @@ -61,6 +64,7 @@ public function __construct($value) } /** + * @psalm-pure * @return mixed * @psalm-return T */ @@ -72,7 +76,7 @@ public function getValue() /** * Returns the enum key (i.e. the constant name). * - * @psalm-external-mutation-free + * @psalm-pure * @return mixed */ public function getKey() @@ -81,6 +85,7 @@ public function getKey() } /** + * @psalm-pure * @psalm-suppress InvalidCast * @return string */ @@ -95,6 +100,7 @@ public function __toString() * * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 * + * @psalm-pure * @psalm-param mixed $variable * @return bool */ @@ -108,7 +114,7 @@ final public function equals($variable = null): bool /** * Returns the names (keys) of all constants in the Enum class * - * @psalm-external-mutation-free + * @psalm-pure * @psalm-return list * @return array */ @@ -120,7 +126,7 @@ public static function keys() /** * Returns instances of the Enum class of all Enum constants * - * @psalm-external-mutation-free + * @psalm-pure * @psalm-return array * @return static[] Constant name in key, Enum instance in value */ @@ -139,7 +145,8 @@ public static function values() /** * Returns all possible values as an array * - * @psalm-external-mutation-free + * @psalm-pure + * * @psalm-return array * @return array Constant name in key, constant value in value */ @@ -160,7 +167,7 @@ public static function toArray() * * @param $value * @psalm-param mixed $value - * @psalm-external-mutation-free + * @psalm-pure * @return bool */ public static function isValid($value) @@ -173,7 +180,7 @@ public static function isValid($value) * * @param $key * @psalm-param string $key - * @psalm-external-mutation-free + * @psalm-pure * @return bool */ public static function isValidKey($key) @@ -189,7 +196,7 @@ public static function isValidKey($key) * @param $value * * @psalm-param mixed $value - * @psalm-external-mutation-free + * @psalm-pure * @return mixed */ public static function search($value) @@ -204,7 +211,7 @@ public static function search($value) * @param array $arguments * * @return static - * @psalm-external-mutation-free + * @psalm-pure * @throws \BadMethodCallException */ public static function __callStatic($name, $arguments) @@ -223,6 +230,7 @@ public static function __callStatic($name, $arguments) * * @return mixed * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @psalm-pure */ public function jsonSerialize() { From 2e58f99a02d251e58b87d78ca20ced5db142151f Mon Sep 17 00:00:00 2001 From: Jarred Stelfox Date: Wed, 12 Feb 2020 09:34:01 -0800 Subject: [PATCH 7/8] Force psalm to accept toArray as pure See previous commit. Psalm isn't actually wrong here, but we are willing to lie to psalm since the impurity is just an internal cache. --- src/Enum.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Enum.php b/src/Enum.php index b502c34..0527356 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -146,6 +146,7 @@ public static function values() * Returns all possible values as an array * * @psalm-pure + * @psalm-suppress ImpureStaticProperty * * @psalm-return array * @return array Constant name in key, constant value in value From fdb1bd56df293631d94dcbb8abd7c8db685d9f8b Mon Sep 17 00:00:00 2001 From: Jarred Stelfox Date: Wed, 12 Feb 2020 09:37:43 -0800 Subject: [PATCH 8/8] Cut: Properties can not be pure This happened due to a bad find replace --- src/Enum.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Enum.php b/src/Enum.php index 0527356..2d2e114 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -31,7 +31,6 @@ abstract class Enum implements \JsonSerializable /** * Store existing constants in a static cache per object. * - * @psalm-pure * * @var array * @psalm-var array>