Collect all remaining callable parameter types for variadic closure parameters instead of using only the matching index#5634
Merged
VincentLanglet merged 4 commits intoMay 11, 2026
Conversation
VincentLanglet
approved these changes
May 10, 2026
…arameters instead of using only the matching index - When a closure with a variadic parameter (e.g. `...$postFiles`) is passed to a function expecting a callable with multiple parameters, collect ALL remaining callable parameter types from the variadic param's index onward and build the proper variadic array type, instead of using only `$callableParameters[$i]` - Fix applies to both `enterAnonymousFunctionWithoutReflection` and `enterArrowFunctionWithoutReflection` in MutatingScope - Also fix `createCallableParameters` in NodeScopeResolver: when the closure's declared parameter is variadic and it is immediately invoked, union all argument types from that index onward instead of only the first argument - Add `buildVariadicArrayTypeFromCallableParameters` helper that correctly handles PHP version (named arguments support) when constructing the array type
5874fc6 to
fa1b686
Compare
staabm
requested changes
May 11, 2026
Contributor
staabm
left a comment
There was a problem hiding this comment.
the origin bug report is about unexpected "Cannot access offset..." errors.
this PR should contain a *RuleTest which shows the "Cannot access offset" no longer is reported
staabm
reviewed
May 11, 2026
Adds a NonexistentOffsetInArrayDimFetchRule test with the original reproducer to verify that "Cannot access offset 'error' on int|string" is no longer reported when accessing offsets on variadic closure parameters passed to typed callable expectations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Collaborator
Author
|
Done. Added a |
- Add bug-9240-php7.php with list<T> assertions for PHP < 8.0 where named arguments are not supported (variadic params produce list types instead of array<int|string, T>) - Add NonexistentOffsetInArrayDimFetchRule test to verify the original "Cannot access offset" error is no longer reported Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
staabm
approved these changes
May 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a closure with a variadic parameter (
...$postFiles) is passed to a function expecting a callable with multiple non-variadic parameters (e.g.Closure(PhpFileArray, PhpFileArray, PhpFileArray): bool), PHPStan incorrectly inferred the variadic parameter's type as the single element type from the matching index (array{error: int, name: string}) instead of the proper variadic array type (array<int|string, array{error: int, name: string}>). This caused false positives like "Cannot access offset 'error' on int|string".Changes
src/Analyser/MutatingScope.php: AddedbuildVariadicArrayTypeFromCallableParameters()helper method that collects element types from all callable parameters at and beyond the variadic parameter's index, unions them, and constructs the correct variadic array type (respecting PHP version for named argument support)src/Analyser/MutatingScope.php: InenterAnonymousFunctionWithoutReflection()andenterArrowFunctionWithoutReflection(), when the closure parameter is variadic, use the new helper instead of directly intersecting with$callableParameters[$i]->getType()src/Analyser/NodeScopeResolver.php: IncreateCallableParameters(), when processing immediately-invoked closures/arrow functions and the callable parameter is variadic, union all argument types from that index onward instead of only using the first argument's typeRoot cause
The bug had two manifestations sharing the same root cause pattern — treating a variadic parameter's index as a 1:1 mapping to callable parameters:
Passed-to-type path (
enterAnonymousFunctionWithoutReflection/enterArrowFunctionWithoutReflection): When callable parameters[PhpFileArray, PhpFileArray, PhpFileArray]existed and the closure had variadic...$postFilesat index 0, only$callableParameters[0]->getType()was used. Sincearray{error: int, name: string}(a constant array) is a subtype ofarray<int|string, mixed>, the intersection collapsed to justarray{error: int, name: string}— a single file array instead of an array of file arrays.Immediately-invoked path (
createCallableParameters): When(function(...$args){})(1, 'hello', 3.14)was analyzed, the variadic parameter's type was only updated withargs[0]'s type (1) instead of the union of all argument types (1|'hello'|3.14).Test
tests/PHPStan/Analyser/nsrt/bug-9240.php: Regression test covering:Fixes phpstan/phpstan#9240