From 31ed326fb113238df762bf9237da50e4613bde32 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Aug 2023 11:55:34 +0200 Subject: [PATCH] CallableTypeHelper - copy variadic parameters if the accepting closure has more parameters --- src/Type/CallableTypeHelper.php | 19 +++++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 14 ++++++++++++++ .../PHPStan/Rules/Functions/data/bug-9699.php | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-9699.php diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index 4672ab2615..e90675d011 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -4,7 +4,9 @@ use PHPStan\Reflection\ParametersAcceptor; use PHPStan\TrinaryLogic; +use function array_key_exists; use function array_merge; +use function count; use function sprintf; class CallableTypeHelper @@ -19,6 +21,23 @@ public static function isParametersAcceptorSuperTypeOf( $theirParameters = $theirs->getParameters(); $ourParameters = $ours->getParameters(); + $lastParameter = null; + foreach ($theirParameters as $theirParameter) { + $lastParameter = $theirParameter; + } + if ( + $lastParameter !== null + && $lastParameter->isVariadic() + && count($theirParameters) < count($ourParameters) + ) { + foreach ($ourParameters as $i => $ourParameter) { + if (array_key_exists($i, $theirParameters)) { + continue; + } + $theirParameters[] = $lastParameter; + } + } + $result = null; foreach ($theirParameters as $i => $theirParameter) { $parameterDescription = $theirParameter->getName() === '' ? sprintf('#%d', $i + 1) : sprintf('#%d $%s', $i + 1, $theirParameter->getName()); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 926d425bfe..3540dc9170 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1421,4 +1421,18 @@ public function testBug6175(): void $this->analyse([__DIR__ . '/data/bug-6175.php'], []); } + public function testBug9699(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1'); + } + + $this->analyse([__DIR__ . '/data/bug-9699.php'], [ + [ + 'Parameter #1 $f of function Bug9699\int_int_int_string expects Closure(int, int, int, string): int, Closure(int, int, int ...): int given.', + 19, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-9699.php b/tests/PHPStan/Rules/Functions/data/bug-9699.php new file mode 100644 index 0000000000..09307d4c47 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-9699.php @@ -0,0 +1,19 @@ += 8.1 + +namespace Bug9699; + +function withVariadicParam(int $a, int $b, int ...$rest): int +{ + return array_sum([$a, $b, ...$rest]); +} + +/** + * @param \Closure(int, int, int, string): int $f + */ +function int_int_int_string(\Closure $f): void +{ + $f(0, 0, 0, ''); +} + +// false negative: expected issue here +int_int_int_string(withVariadicParam(...));