diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 20317a14053..427c5bee51c 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -516,7 +516,27 @@ public static function selectFromArgs( } if ($originalArg->unpack) { $unpack = true; - $types[$index] = $type->getIterableValueType(); + $constantArrays = $type->getConstantArrays(); + if (count($constantArrays) > 0) { + foreach ($constantArrays as $constantArray) { + $values = $constantArray->getValueTypes(); + foreach ($constantArray->getKeyTypes() as $j => $keyType) { + $valueType = $values[$j]; + $valueIndex = $keyType->getValue(); + if (is_string($valueIndex)) { + $hasName = true; + } else { + $valueIndex = $i + $j; + } + + $types[$valueIndex] = isset($types[$valueIndex]) + ? TypeCombinator::union($types[$valueIndex], $valueType) + : $valueType; + } + } + } else { + $types[$index] = $type->getIterableValueType(); + } } else { $types[$index] = $type; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-8257.php b/tests/PHPStan/Analyser/nsrt/bug-8257.php new file mode 100644 index 00000000000..c674e66ad23 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8257.php @@ -0,0 +1,28 @@ + $signature + * @param mixed $source + * @return ( + * $signature is class-string + * ? T + * : mixed + * ) + */ + public function map(string $signature, $source); +} + +/** @var TreeMapper $tm */ +$tm; + +class A {} + +assertType('Bug8257\A', $tm->map(...[A::class, []])); diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index a1db3f755a2..f8499b9c742 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -579,6 +579,12 @@ public function testBug13440(): void $this->analyse([__DIR__ . '/data/bug-13440.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug12363(): void + { + $this->analyse([__DIR__ . '/../Methods/data/bug-12363.php'], []); + } + public function testNewStaticWithConsistentConstructor(): void { $this->analyse([__DIR__ . '/data/instantiation-new-static-consistent-constructor.php'], [ diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 77ef9843bfe..d06950ad68f 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2735,4 +2735,10 @@ public function testBug10559(): void $this->analyse([__DIR__ . '/data/bug-10559.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug12363(): void + { + $this->analyse([__DIR__ . '/data/bug-12363.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-12363.php b/tests/PHPStan/Rules/Functions/data/bug-12363.php new file mode 100644 index 00000000000..cf4c801e75c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-12363.php @@ -0,0 +1,26 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug12363; + +/** + * @template Y of 'a'|'b' + * @param Y $y + */ +function f(int $x, string $y = 'a'): void {} + +// Spreading associative array with required + optional template param +f(...['x' => 5, 'y' => 'b']); + +// Without spread - should also work +f(5, 'b'); + +/** + * @template Y of 'a'|'b' + * @param Y $y + */ +function g(string $y = 'a'): void {} + +// Without preceding required arg - already works +g(...['y' => 'b']); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 3460563a480..99480a40611 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3908,6 +3908,19 @@ public function testBug1501(): void $this->analyse([__DIR__ . '/data/bug-1501.php'], []); } + public function testBug7369(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-7369.php'], [ + [ + 'Cannot call method isAllowed() on bool.', + 40, + ], + ]); + } + public function testBug11463(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/bug-12363.php b/tests/PHPStan/Rules/Methods/data/bug-12363.php new file mode 100644 index 00000000000..68156b9669a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12363.php @@ -0,0 +1,52 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug12363Methods; + +/** + * @template Y of 'a'|'b' + */ +class A +{ + /** + * @param Y $y + */ + public function __construct( + public readonly int $x, + public readonly string $y = 'a', + ) { + } +} + +$a = new A(...['x' => 5, 'y' => 'b']); + +$aa = new A(...[5, 'y' => 'b']); + +$aaa = new A(...[5, 'b']); + +$aaaa = new A(...[1 => 5, 2 => 'b']); + +/** + * @template Y of 'a'|'b' + */ +class B +{ + /** + * @param Y $y + */ + public function __construct( + public readonly int $init, + public readonly int $x, + public readonly string $y = 'a', + ) { + } +} + +$a = new B(1, ...['x' => 5, 'y' => 'b']); + +$aa = new B(1, ...[5, 'y' => 'b']); + +$aaa = new B(1, ...[5, 'b']); + +$aaaa = new B(1, ...[1 => 5, 2 => 'b']); diff --git a/tests/PHPStan/Rules/Methods/data/bug-7369.php b/tests/PHPStan/Rules/Methods/data/bug-7369.php new file mode 100644 index 00000000000..224a2493fa7 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-7369.php @@ -0,0 +1,43 @@ +access('view', null, true)->isAllowed(); +$class->access('view', null, false)->isAllowed(); + +$params = ['view', null, true]; +$class->access(...$params)->isAllowed();