From d49dd38e3f8c8790e1f67434f69d320ae1194eeb Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 31 Mar 2022 14:32:32 +0200 Subject: [PATCH 1/2] Support named arguments when resolving template parameters Resolves phpstan/phpstan#5262 --- .../GenericParametersAcceptorResolver.php | 4 ++- src/Reflection/ParametersAcceptorSelector.php | 12 ++++--- .../Analyser/NodeScopeResolverTest.php | 6 ++++ tests/PHPStan/Analyser/data/bug-5262.php | 36 +++++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5262.php diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index 2e79b78c9b..c26b40cafe 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -12,7 +12,7 @@ class GenericParametersAcceptorResolver /** * @api - * @param Type[] $argTypes + * @param array $argTypes */ public static function resolve(array $argTypes, ParametersAcceptor $parametersAcceptor): ParametersAcceptor { @@ -21,6 +21,8 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc foreach ($parametersAcceptor->getParameters() as $i => $param) { if (isset($argTypes[$i])) { $argType = $argTypes[$i]; + } elseif (isset($argTypes[$param->getName()])) { + $argType = $argTypes[$param->getName()]; } elseif ($param->getDefaultValue() !== null) { $argType = $param->getDefaultValue(); } else { diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 899a8a87d1..15d13d5f63 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -18,6 +18,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use function array_key_last; use function array_slice; use function count; use function sprintf; @@ -161,13 +162,14 @@ public static function selectFromArgs( } } - foreach ($args as $arg) { + foreach ($args as $i => $arg) { $type = $scope->getType($arg->value); + $index = $arg->name !== null ? $arg->name->toString() : $i; if ($arg->unpack) { $unpack = true; - $types[] = $type->getIterableValueType(); + $types[$index] = $type->getIterableValueType(); } else { - $types[] = $type; + $types[$index] = $type; } } @@ -175,7 +177,7 @@ public static function selectFromArgs( } /** - * @param Type[] $types + * @param array $types * @param ParametersAcceptor[] $parametersAcceptors */ public static function selectFromTypes( @@ -246,7 +248,7 @@ public static function selectFromTypes( break; } - $type = $types[count($types) - 1]; + $type = $types[array_key_last($types)]; } else { $type = $types[$i]; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 4d22b68c2b..7f5ccce0fb 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -847,6 +847,12 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6917.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6936-limit.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6927.php'); + + if (PHP_VERSION_ID < 80000) { + return; + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5262.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5262.php b/tests/PHPStan/Analyser/data/bug-5262.php new file mode 100644 index 0000000000..229d9fbeb2 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5262.php @@ -0,0 +1,36 @@ + $testclass + * @return T + */ +function test(bool $optional = false, string $testclass = TestBase::class): TestBase +{ + return new $testclass(); +} + +class TestBase +{ +} + +class TestChild extends TestBase +{ + public function hello(): string + { + return 'world'; + } +} + +function runTest(): void +{ + assertType('Bug5262\TestChild', test(false, TestChild::class)); + assertType('Bug5262\TestChild', test(false, testclass: TestChild::class)); + assertType('Bug5262\TestChild', test(optional: false, testclass: TestChild::class)); + assertType('Bug5262\TestChild', test(testclass: TestChild::class, optional: false)); + assertType('Bug5262\TestChild', test(testclass: TestChild::class)); +} From a6ae08ee8cc5a6bbca110b01fd7e1393aa18cdea Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 31 Mar 2022 14:44:28 +0200 Subject: [PATCH 2/2] Fix cs --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7f5ccce0fb..ff72a3fd2c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -846,13 +846,12 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6917.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6936-limit.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6927.php'); - if (PHP_VERSION_ID < 80000) { - return; + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5262.php'); } - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5262.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6927.php'); } /**