Collect all remaining callable parameter types for variadic closure parameters instead of using only the matching index#5634
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
…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
VincentLanglet
approved these changes
May 10, 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