Skip to content

Fix #12363: Spreading an associative array into a generic function/method call may restrict optional arguments to their default values.#5079

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-gaiokkv
Open

Fix #12363: Spreading an associative array into a generic function/method call may restrict optional arguments to their default values.#5079
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-gaiokkv

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When spreading an associative array into a function/method call, template types on optional parameters were incorrectly resolved to their default values instead of the actual provided values. For example, f(...['x' => 5, 'y' => 'b']) where $y is a template Y of 'a'|'b' with default 'a' would report Parameter $y expects 'a', 'b' given as a false positive.

Changes

  • Modified src/Reflection/ParametersAcceptorSelector.php in selectFromArgs(): when processing an unpacked argument that is a constant array with all string keys, expand it into individual named type entries instead of collapsing into a single getIterableValueType() call
  • Added regression test data file tests/PHPStan/Rules/Functions/data/bug-12363.php for function calls
  • Added regression test data file tests/PHPStan/Rules/Methods/data/bug-12363.php for class constructor calls
  • Added test methods in CallToFunctionParametersRuleTest and InstantiationRuleTest

Root cause

In ParametersAcceptorSelector::selectFromArgs(), when encountering an unpacked argument like ...['x' => 5, 'y' => 'b'], the code called $type->getIterableValueType() which merged all values into int|'b' and assigned it to positional index 0. This meant GenericParametersAcceptorResolver::resolve() only saw a type for parameter 0 ($x), while parameter 1 ($y) had no argument type and fell back to its default value 'a' for template inference. The template Y was therefore resolved to 'a' instead of 'b'.

The fix detects constant arrays with all string keys during unpacking and expands them into individual named entries in the $types array. This allows GenericParametersAcceptorResolver::resolve() to correctly match each value to its corresponding parameter by name, resulting in proper template type inference.

Test

Regression tests verify that spreading associative arrays into functions and class constructors with template-typed optional parameters no longer produces false positive errors. Tests cover:

  • Function with required int $x + optional template Y $y = 'a' called via f(...['x' => 5, 'y' => 'b'])
  • Function with only optional template param called via g(...['y' => 'b']) (was already working, ensures no regression)
  • Class constructor with the same pattern

Fixes phpstan/phpstan#12363

- When spreading an associative constant array into a function/method call,
  ParametersAcceptorSelector::selectFromArgs() now expands string-keyed
  constant arrays into individual named type entries instead of collapsing
  them into a single getIterableValueType() call
- This allows GenericParametersAcceptorResolver to correctly map argument
  types to parameters by name, preventing template types from being
  incorrectly inferred from default values
- Added regression tests for both function calls and class constructors

Closes phpstan/phpstan#12363
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.

1 participant