Skip to content

Expand unpacked args into one value per array item - #7038

Open
SanderMuller wants to merge 1 commit into
phpstan:2.3.xfrom
SanderMuller:unpacked-array-map-args
Open

SanderMuller wants to merge 1 commit into
phpstan:2.3.xfrom
SanderMuller:unpacked-array-map-args

Conversation

@SanderMuller

Copy link
Copy Markdown
Contributor

Closes phpstan/phpstan#15620

An unpacked arg passes one value per item of its array. array_map() and immediately invoked closures typed it as one value. So the callback parameters and the return type of array_map() were wrong, and unpacking a general array reported a false argument.type error.

The new UnpackedArgumentTypes::expand() turns the arg types into one type per position:

  • A list shape gives one type per required item. With a union of shapes, each position gets the union of the members, up to the smallest number of required items. The items after that go into a rest type.
  • A general array, or a shape that is not a list, gives a rest type, because the number of values is not known.

These places use it:

  • ContextualClosureParameterResolver, for array_map() callbacks and immediately invoked closures. A rest type becomes a variadic parameter.
  • ParametersAcceptorSelector and ArrayMapParameterClosureTypeExtension. With a rest type, the callback keeps the type from the signature.
  • ArrayMapFunctionReturnTypeExtension. With a rest type, it returns the declared type.

When the expansion gives a known number of arrays, array_map() now adds null for the shorter arrays. #6874 does the same for arrays that are not unpacked, but it skipped unpacked args.

ContextualClosureParameterResolver and ParametersAcceptorSelector have Turbo twins. Both C++ twins call the PHP helper.

Reproducer from the issue, at level 8:

Line Before After
9 array{4, 5, 6} 4|5|6
13 array{1, 2} 1
16 non-empty-list<array{4, 5, 6}> non-empty-list<4|5|6>
18 Parameter #1 $callback of function array_map expects (callable(): mixed)|null, Closure(int, int): int given. no error

🤖 Generated with Claude Code

An unpacked arg passes one value per item of its array. array_map() and
immediately invoked closures typed it as one value, so the callback
parameters and the return type of array_map() were wrong. Unpacking a
general array also reported a false argument.type error.

When the number of values is not known, the callback keeps the type from
the signature and array_map() returns its declared type. When it is known,
array_map() adds null for the shorter arrays, as it does for arrays that
are not unpacked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Unpacked args to array_map() and immediately invoked closures are typed as one value

1 participant