From 5f0b1ccfa47060c209ead7116005214183c0e56f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 9 Nov 2024 10:05:14 +0100 Subject: [PATCH] Too-wide return type - do not report void in PHPDoc union type --- .../TooWideFunctionReturnTypehintRule.php | 6 +-- .../TooWideMethodReturnTypehintRule.php | 6 +-- .../TooWideFunctionReturnTypehintRuleTest.php | 10 ++++ .../TooWideMethodReturnTypehintRuleTest.php | 12 +++++ .../data/bug-11980-function.php | 49 +++++++++++++++++ .../Rules/TooWideTypehints/data/bug-11980.php | 53 +++++++++++++++++++ 6 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 217d8576cd..b0b11a10ae 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -11,6 +11,7 @@ use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +use PHPStan\Type\VoidType; use function count; use function sprintf; @@ -48,16 +49,13 @@ public function processNode(Node $node, Scope $scope): array foreach ($returnStatements as $returnStatement) { $returnNode = $returnStatement->getReturnNode(); if ($returnNode->expr === null) { + $returnTypes[] = new VoidType(); continue; } $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } - if (count($returnTypes) === 0) { - return []; - } - $returnType = TypeCombinator::union(...$returnTypes); $messages = []; diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index ebb22df8ae..b73fa09641 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -12,6 +12,7 @@ use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +use PHPStan\Type\VoidType; use function count; use function sprintf; @@ -74,16 +75,13 @@ public function processNode(Node $node, Scope $scope): array foreach ($returnStatements as $returnStatement) { $returnNode = $returnStatement->getReturnNode(); if ($returnNode->expr === null) { + $returnTypes[] = new VoidType(); continue; } $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } - if (count($returnTypes) === 0) { - return []; - } - $returnType = TypeCombinator::union(...$returnTypes); if ( !$method->isPrivate() diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index b914db3141..9c86812e92 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -51,4 +51,14 @@ public function testRule(): void ]); } + public function testBug11980(): void + { + $this->analyse([__DIR__ . '/data/bug-11980-function.php'], [ + [ + 'Function Bug11980Function\process2() never returns void so it can be removed from the return type.', + 34, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 9e9588d219..cad3ca757f 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -248,4 +248,16 @@ public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, bool $this->analyse([__DIR__ . '/data/method-too-wide-return-always-check-final.php'], $expectedErrors); } + public function testBug11980(): void + { + $this->checkProtectedAndPublicMethods = true; + $this->alwaysCheckFinal = true; + $this->analyse([__DIR__ . '/data/bug-11980.php'], [ + [ + 'Method Bug11980\Demo::process2() never returns void so it can be removed from the return type.', + 37, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php new file mode 100644 index 0000000000..0db93a1cfb --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php @@ -0,0 +1,49 @@ +> $tokens + * @param int $stackPtr + * + * @return int|void + */ +function process($tokens, $stackPtr) +{ + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } +} + +/** + * @param array> $tokens + * @param int $stackPtr + * + * @return int|void + */ +function process2($tokens, $stackPtr) +{ + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return null; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } +} diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php new file mode 100644 index 0000000000..99ab186f62 --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php @@ -0,0 +1,53 @@ +> $tokens + * @param int $stackPtr + * + * @return int|void + */ + public function process($tokens, $stackPtr) + { + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } + } + + /** + * @param array> $tokens + * @param int $stackPtr + * + * @return int|void + */ + public function process2($tokens, $stackPtr) + { + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return null; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } + } +}