Add IDE type narrowing to generated validator mixins#1802
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1802 +/- ##
=========================================
Coverage 97.12% 97.12%
Complexity 1067 1067
=========================================
Files 198 198
Lines 2501 2501
=========================================
Hits 2429 2429
Misses 72 72 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@henriquemoody these are in draft because of the branch-alias (we need to amend the real composer versions when we do the release dance). However, although not packagist-ready yet, they are open for review of the code. I requested review only here to avoid spam but you're free to comment in any of the 3. |
henriquemoody
left a comment
There was a problem hiding this comment.
This is trully trully awesome!!
| 'For comparison with {{now|raw}}, {{subject}} must not be a datetime in the format {{sample|raw}}', | ||
| self::TEMPLATE_WRONG_FORMAT, | ||
| )] | ||
| #[Assurance(type: ['string', DateTimeInterface::class])] |
There was a problem hiding this comment.
| #[Assurance(type: ['string', DateTimeInterface::class])] | |
| #[Assurance(type: ['string', 'integer', DateTimeInterface::class])] |
I think we also allow passing timestamps
There was a problem hiding this comment.
It's not on the tests though, so we might want to tackle refining that later?
| '{{subject}} must be present', | ||
| '{{subject}} must not be present', | ||
| )] | ||
| #[Assurance(type: ['array', ArrayAccess::class])] |
There was a problem hiding this comment.
Don't we have any "array key exists" assurance? It would be quite important
There was a problem hiding this comment.
That's a great question.
For IDE support, no (I don't think it's possible just with mixin annotations).
For phpstan analyze, we can do that (but it's not fully annotated in KeyExists).
| '{{subject}} must be present', | ||
| '{{subject}} must not be present', | ||
| )] | ||
| #[Assurance(type: 'object')] |
There was a problem hiding this comment.
Don't we have any assurance for "property exists"?
| '{{subject}} must be a scalar', | ||
| '{{subject}} must not be a scalar', | ||
| )] | ||
| #[Assurance(type: 'int|float|bool|string', exact: true)] |
There was a problem hiding this comment.
| #[Assurance(type: 'int|float|bool|string', exact: true)] | |
| #[Assurance(type: 'scalar', exact: true)] |
Or am I missing something?
There was a problem hiding this comment.
For this PR, it's the same.
For FluentAnalysis, there are some things we don't do yet (like unpacking scalar into the list for multi-node hop narrowing).
So, in the future, we might have to pre-unpack all types in declaration instead of using the more elegant one, just to give FluentAnalysis more surface. Or we might want to implement the unpacking in FluentAnalysis.
|
|
||
| #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] | ||
| #[Assurance(compose: AssuranceCompose::Intersect, exact: true)] | ||
| final readonly class ShortCircuit implements Validator |
There was a problem hiding this comment.
Because of how this validator works, I'm not sure we should assure something here. What do you think?
There was a problem hiding this comment.
The FluentAnalysis extension can do this one. I ported the #[Assurance blocks from that PR, and some of them have no outcome in this PR.
This is one of them.
Perhaps it will be better if I remove the ones that do not yield any significant narrowing for this PR.
There was a problem hiding this comment.
I understood that it can, but should it? Because we short circuit, isn't it arguable if it will make all those assertions? I'm not sure, not saying we shouldn't, but I'm not sure
There was a problem hiding this comment.
For this PR, it's a null-op. It only matters for FluentAnalysis. So I'll remove it, and we discuss when I integrate it!
|
|
||
| #[Composable(without: [All::class, Key::class, Property::class, Not::class, NullOr::class, UndefOr::class])] | ||
| #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] | ||
| #[Assurance(compose: AssuranceCompose::Intersect, composeRange: [1, 1], exact: true)] |
There was a problem hiding this comment.
I just want to say that it's incredibly awesome that you managed to make this work!
There was a problem hiding this comment.
Don't get too excited, this has no deep effect in this PR.
I'm considering now dropping the Assurances that would only work on #1730 to avoid confusion, then adding them later.
b1b9c68 to
07ccc7d
Compare
Make validator chains narrow types for IDEs and PHPStan without the FluentAnalysis extension, driven entirely by the generated src/Mixins PHPDoc. - Annotate src/Validators with #[Assurance] / #[AssuranceSubject] declaring each rule's assured type. - Regenerate src/Mixins: Chain becomes generic (@template-covariant TSure); static entry methods narrow to Chain<concrete>; assert()/check() carry an unconditional @phpstan-assert TSure. Container rules (key/property/length/ max/min) and the concrete prefix forms (nullOrIntType, keyIntType, allIntType) narrow; argument-wrapping and compose forms stay Chain<mixed> so a raw (non-fluent) Validator argument is still accepted. - isValid() intentionally does not narrow: its only conditional form is a two-way guard, unsound for inexact rules on the false branch. - Add the tests/inference static-narrowing suite (no extension config) and run it in CI; scope the phpstan/phpcs accommodations for generated mixins.
|
I have removed the Hopefully, this would make clear what IDEs can do and what they cannot (the static-narrowing.php file is a good reference card though, and remains the same). |
Part of a PR group: Fluent - FluentGen - Validation
Make validator chains narrow types for IDEs and PHPStan without the FluentAnalysis extension, driven entirely by the generated src/Mixins PHPDoc.
This PR group adds advanced static (no PHPStan extension needed) type support and narrowing for fluent chains, pioneering it for Validation (StringFormatter should also be compatible with this approach)
In the example above, the devsense extension for VSCode is correctly infering an
int[](iterable int) from aeach(int())fluent validation chain.More examples in
tests/inference/assertions/static-narrowing.php.Notes: