-
-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added new type-safe API that allows validating a given value against a given class-string<Enum>
#143
base: master
Are you sure you want to change the base?
Added new type-safe API that allows validating a given value against a given class-string<Enum>
#143
Conversation
These added tests verify that: * `Enum::assert*` methods can be used in a pure form * `Enum::from*` methods can be used in a pure form * `Enum#__construct()` properly binds the instance type of the enumerable with the input value * `Enum::assert*` methods currently **CANNOT** bind the input to an enumerable potential value (BUG) * `Enum::from*` methods currently **CANNOT** bind the input to an enumerable potential value (BUG) In addition to that, `psalm.xml` has been made a bit stricter: * `resolveFromConfigFile` was removed (we were using the default value) * `findUnusedPsalmSuppress` was added, allowing us to find whether intentional suppressions are now invalid. This allows us to verify negative scenarios, in which we **expect** a type error to appear * `restrictReturnTypes` better refines templated types, requiring inputs/outputs to match for those too
…ound to any template These functions had a `T` template parameter in them, but this `T` is completely decoupled from the one defined at class-level (and therefore at `Enum` instance level, rather than statically): * `Enum::from()` * `Enum::isValid()` * `Enum::assertValidValue()` * `Enum::assertValidValueReturningKey()` (internal detail) In practice, this means that myclabs#135 (Added new named constructor to create enum from mixed) was not a valid approach to infer types for enumerable values, when performed statically. A new approach will be attempted, but the current one will likely need to be scrapped for now. In practice, `@psalm-assert`, `@psalm-assert-if-true` and `@psalm-return static<T>` had no effect on these methods, due to a design-level issue that wasn't spotted (by myself either) at review.
…a given `class-string<Enum>` This patch introduces two new methods: * `Enum::isValidEnumValue()` * `Enum::assertValidEnumValue()` The `Enum::isValidEnumValue()` is still wonky due to vimeo/psalm#5372, but overall, this allows for matching against other `Enum` sub-types, by validating that a given `$value` is effectively a subtype of `T` within an `Enum<T>`. The previous approach did **not** work, because static method types are not coupled with the class they are put on (they are effectively namespaced functions, and not much else). In here, we also: * completed test coverage for `Enum::from()` * pinned `vimeo/psalm:4.6.2`, mostly for build stability * added coverage mapping for `phpunit.xml`
@@ -28,6 +28,6 @@ | |||
"require-dev": { | |||
"phpunit/phpunit": "^9.5", | |||
"squizlabs/php_codesniffer": "1.*", | |||
"vimeo/psalm": "^4.6.2" | |||
"vimeo/psalm": "4.6.2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pinned: changing this dependency is too fragile for builds to be kept stable.
My suggestion is to instead use dependabot to regularly keep it updated.
} | ||
|
||
/** @psalm-pure */ | ||
function canCallFromWithKnownValue(): InstantiatedEnum |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice how we CANNOT infer InstantiatedEnum<'C'>
from this signature: this is a known problem with Enum::from()
(static method), as I've described in the patch overview.
@@ -11,7 +11,7 @@ | |||
* @method static PureEnum C() | |||
* | |||
* @psalm-immutable | |||
* @psalm-template T of 'A'|'B' | |||
* @psalm-template T of 'A'|'C' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a bug with this file - otherwise all good.
* @psalm-return 'A'|'C' | ||
* | ||
* @psalm-suppress MixedReturnStatement | ||
* @psalm-suppress MixedInferredReturnType at the time of this writing, we did not yet find |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is kinda oudated - perhaps I can drop the static analysis check overall, now that I designed Enum::assertValidEnumValue()
🤔
* @psalm-return 'A'|'C' | ||
* | ||
* @psalm-suppress MixedReturnStatement | ||
* @psalm-suppress MixedInferredReturnType at the time of this writing, we did not yet find |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is kinda oudated - perhaps I can drop the static analysis check overall, now that I designed Enum::isValidEnumValue()
🤔
* @psalm-return 'A'|'C'|1 | ||
* | ||
* @psalm-suppress InvalidReturnType https://github.com/vimeo/psalm/issues/5372 | ||
* @psalm-suppress InvalidReturnStatement https://github.com/vimeo/psalm/issues/5372 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vimeo/psalm#5372 is still kinda blocking this
* @return bool | ||
* | ||
* deprecated use {@see Enum::isValidEnumValue()} instead |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not yet @deprecated
- should I mark it as such, or should I drop the comment and leave it be?
* @psalm-pure | ||
* @param mixed $value | ||
* | ||
* deprecated use {@see Enum::assertValidEnumValue()} instead |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not yet @deprecated
- should I mark it as such, or should I drop the comment and leave it be?
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xmlns="https://getpsalm.org/schema/config" | ||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" | ||
restrictReturnTypes="true" | ||
findUnusedPsalmSuppress="true" | ||
totallyTyped="true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Psalm docs say this is deprecated, might be dropped as level is max already
https://psalm.dev/docs/running_psalm/configuration/#totallytyped
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WithtotallyTyped="true"
the default level is 1
, but without totallyTyped
it would be 2
: https://github.com/vimeo/psalm/pull/5340/files
So I think we should replace totallyTyped="true"
with errorLevel="1"
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, it's a bit out of scope but has been touched already so 🤷🏾
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I generally remove errorLevel
wherever I go: it's just confusing.
Hey! Just to be sure I understand, these 2 new methods are only useful for static analysis right? If we can avoid that that would be great though (I'm not sold on deprecating current methods which are simpler to use). How about: public static function isValid($value, ?string $enumType = null) The new parameter would be optional, and only those using static analysis would go through the trouble of providing the class name. Would that work? |
Heads up: spent a lot of time debugging issues around this today, and will certainly work more on the patch as soon as I can, but I think what I'll do for now is removing |
Agree, probably better to just leave them be.
That is an excellent question, and I do indeed need to try it out! 👍 |
I recently stumbled upon this as well. If there is something I can help with @Ocramius, let me know. |
This patch introduces two new methods:
Enum::isValidEnumValue()
Enum::assertValidEnumValue()
The
Enum::isValidEnumValue()
is still wonky due to vimeo/psalm#5372, butoverall, this allows for matching against other
Enum
sub-types, by validating that a given$value
is effectively a subtype of
T
within anEnum<T>
.The previous approach did not work, because static method types are not coupled with the class they
are put on (they are effectively namespaced functions, and not much else).
Detailed explanation of what was done is available commit-by-commit, but the idea is that it is impossible to validate
T
for a givenEnum<T>
when a static method is used to do so, because static methods, by definition, are not coupled to the class they live on.Therefore, I had to be a bit more creative:
Enum::assertValidEnumValue(ConcreteEnumClass::class, $value)
is to be used instead.TODOs:
assert-if-true
and template types that involve union types are not correctly recognized vimeo/psalm#5372/cc @michaelpetri @guidobonuzzi @DavideBicego