Skip to content

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When spreading a constant array with optional named keys into a constructor/function call (e.g., new Foo(...$manipulations) where $manipulations is non-empty-array{width?: int, bgColor?: string}), PHPStan incorrectly reported a type error like "Parameter #1 $width expects int|null, int|string given." The fix ensures named optional keys are properly expanded as individual named arguments instead of falling through to a generic fallback that unions all value types.

Changes

  • Modified src/Rules/FunctionCallParametersCheck.php: Changed the optional key skip condition (line 182) to only skip optional keys that don't have a named key ($keyArgumentName === null). Named optional keys are now expanded as individual named arguments with their correct per-key types.
  • Added regression test in tests/PHPStan/Rules/Classes/data/bug-14097.php
  • Added test method in tests/PHPStan/Rules/Classes/InstantiationRuleTest.php
  • Updated CLAUDE.md with documentation about the spread argument expansion pattern

Root cause

In FunctionCallParametersCheck::check(), when expanding a spread constant array argument, optional keys were unconditionally skipped (line 182-184). When ALL keys were optional (as happens after !empty() narrowing of an array with all-optional keys), no arguments were expanded. The code then fell into a fallback path (lines 195-203) that used getIterableValueType() — the union of ALL value types — as a single generic unpacked argument. This lost the key-to-type correspondence: {width?: int, bgColor?: string} became int|string instead of mapping width -> int and bgColor -> string to their respective parameters.

The fix preserves the skip for optional integer-keyed (positional) entries but expands optional string-keyed (named) entries as named arguments, maintaining the correct type mapping per parameter.

Test

Added tests/PHPStan/Rules/Classes/data/bug-14097.php reproducing the exact scenario from the issue: an array built up conditionally with $manipulations['width'] = (int)$matches['w'] and $manipulations['bgColor'] = $matches['rgb'], then spread into new Manipulations(...$manipulations) inside an !empty($manipulations) check. The test verifies no errors are reported.

Fixes phpstan/phpstan#14097

- Fixed FunctionCallParametersCheck skipping optional named keys during
  spread expansion, causing fallback to union all value types
- Optional keys with string names are now expanded as named arguments
  so each key's type is checked against the correct parameter
- New regression test in tests/PHPStan/Rules/Classes/data/bug-14097.php

Fixes phpstan/phpstan#14097
@ondrejmirtes ondrejmirtes merged commit aaaba11 into 2.1.x Feb 12, 2026
631 of 641 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-pszatkl branch February 12, 2026 19:05
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.

Incorrect type with empty check

2 participants