Skip to content

Add IDE type narrowing to generated validator mixins - #1802

Open
alganet wants to merge 1 commit into
mainfrom
ide-narrowing
Open

Add IDE type narrowing to generated validator mixins#1802
alganet wants to merge 1 commit into
mainfrom
ide-narrowing

Conversation

@alganet

@alganet alganet commented Jun 26, 2026

Copy link
Copy Markdown
Member

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.

  • 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; 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 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.

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)

image

In the example above, the devsense extension for VSCode is correctly infering an int[] (iterable int) from a each(int()) fluent validation chain.

More examples in tests/inference/assertions/static-narrowing.php.


Notes:

  • Most of the big diff is generated mixins, the PR is actually small.
  • This work is related, but distinct from Integrate Fluent, FluentGen and FluentAnalysis #1730. FluentAnalysis offers dynamic type narrowing with much richer narrowings (but IDEs can't run the extension). This one offers basic, static narrowing for IDEs such as devsense (tested) and IntelliJ (untested).

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.14%. Comparing base (639f02f) to head (3d288ea).

Additional details and impacted files
@@            Coverage Diff            @@
##               main    #1802   +/-   ##
=========================================
  Coverage     97.14%   97.14%           
  Complexity     1096     1096           
=========================================
  Files           198      198           
  Lines          2554     2554           
=========================================
  Hits           2481     2481           
  Misses           73       73           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alganet

alganet commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

@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 henriquemoody left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is trully trully awesome!!

Comment thread src/Validators/TrueVal.php
Comment thread src/Validators/Undef.php Outdated
Comment thread src/Validators/BoolVal.php
Comment thread src/Validators/DateTimeDiff.php Outdated
Comment thread src/Validators/FalseVal.php
Comment thread src/Validators/KeyExists.php
Comment thread src/Validators/PropertyExists.php
Comment thread src/Validators/ScalarVal.php Outdated
'{{subject}} must be a scalar',
'{{subject}} must not be a scalar',
)]
#[Assurance(type: 'int|float|bool|string', exact: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[Assurance(type: 'int|float|bool|string', exact: true)]
#[Assurance(type: 'scalar', exact: true)]

Or am I missing something?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't instruct IDEs into unpacking scalar into the list, and some of they don't do that. PHPStan does, and the FluentAnalysis extension can be made to do that by using the types package from phpstan.

For that reason, I would prefer using the unpacked explicit list. scalar is an alias for that list, and if by exposing the list we target more IDEs, that would be my preference.

Comment thread src/Validators/ShortCircuit.php
Comment thread src/Validators/Templated.php Outdated
@alganet
alganet force-pushed the ide-narrowing branch 3 times, most recently from 07ccc7d to 3ec0d33 Compare June 26, 2026 19:43
@alganet

alganet commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

I have removed the #[Assurance attributes that are not relevant for this PR, keeping only the ones that actively contribute to the IDE-only static narrowing. The ones removed belong to the full FluentAnalysis phpstan-extension integration that doesn't really affect IDE behavior.

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).

@alganet
alganet force-pushed the ide-narrowing branch 2 times, most recently from 67ea602 to d26a84c Compare September 5, 2026 17:29
@alganet
alganet marked this pull request as ready for review September 5, 2026 17:29
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants