diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index 5d5431a964..307a3ec528 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\TooWideTypehints; +use PhpParser\Node\Expr\ConstFetch; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; @@ -25,8 +26,10 @@ use PHPStan\Type\VerbosityLevel; use PHPStan\Type\VoidType; use function count; +use function in_array; use function lcfirst; use function sprintf; +use function strtolower; #[AutowiredService] final class TooWideTypeCheck @@ -205,6 +208,18 @@ public function checkFunctionReturnType( $returnType = TypeCombinator::union(...$returnTypes); + if ( + count($returnStatements) === 1 + && $returnStatements[0]->getReturnNode()->expr instanceof ConstFetch + && in_array(strtolower($returnStatements[0]->getReturnNode()->expr->name->toString()), ['true', 'false'], true) + && ( + $nativeFunctionReturnType->isBoolean()->yes() + || $phpDocFunctionReturnType->isBoolean()->yes() + ) + ) { + return []; + } + $unionMessagePattern = sprintf('%s never returns %%s so it can be removed from the return type.', $functionDescription); $boolMessagePattern = sprintf('%s never returns %%s so the return type can be changed to %%s.', $functionDescription); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index edae4aeeab..1247a58c51 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1099,23 +1099,15 @@ public function testBug8376(): void public function testAssertDocblock(): void { $errors = $this->runAnalyse(__DIR__ . '/nsrt/assert-docblock.php'); - $this->assertCount(8, $errors); - $this->assertSame('Function AssertDocblock\validateStringArrayIfTrue() never returns false so the return type can be changed to true.', $errors[0]->getMessage()); - $this->assertSame(17, $errors[0]->getLine()); - $this->assertSame('Function AssertDocblock\validateStringArrayIfFalse() never returns true so the return type can be changed to false.', $errors[1]->getMessage()); - $this->assertSame(25, $errors[1]->getLine()); - $this->assertSame('Function AssertDocblock\validateStringOrIntArray() never returns true so the return type can be changed to false.', $errors[2]->getMessage()); - $this->assertSame(34, $errors[2]->getLine()); - $this->assertSame('Function AssertDocblock\validateStringOrNonEmptyIntArray() never returns true so the return type can be changed to false.', $errors[3]->getMessage()); - $this->assertSame(44, $errors[3]->getLine()); - $this->assertSame('Call to method AssertDocblock\A::testInt() with string will always evaluate to false.', $errors[4]->getMessage()); - $this->assertSame(218, $errors[4]->getLine()); - $this->assertSame('Call to method AssertDocblock\A::testNotInt() with string will always evaluate to true.', $errors[5]->getMessage()); - $this->assertSame(224, $errors[5]->getLine()); - $this->assertSame('Call to method AssertDocblock\A::testInt() with int will always evaluate to true.', $errors[6]->getMessage()); - $this->assertSame(232, $errors[6]->getLine()); - $this->assertSame('Call to method AssertDocblock\A::testNotInt() with int will always evaluate to false.', $errors[7]->getMessage()); - $this->assertSame(238, $errors[7]->getLine()); + $this->assertCount(4, $errors); + $this->assertSame('Call to method AssertDocblock\A::testInt() with string will always evaluate to false.', $errors[0]->getMessage()); + $this->assertSame(218, $errors[0]->getLine()); + $this->assertSame('Call to method AssertDocblock\A::testNotInt() with string will always evaluate to true.', $errors[1]->getMessage()); + $this->assertSame(224, $errors[1]->getLine()); + $this->assertSame('Call to method AssertDocblock\A::testInt() with int will always evaluate to true.', $errors[2]->getMessage()); + $this->assertSame(232, $errors[2]->getLine()); + $this->assertSame('Call to method AssertDocblock\A::testNotInt() with int will always evaluate to false.', $errors[3]->getMessage()); + $this->assertSame(238, $errors[3]->getLine()); } #[RequiresPhp('>= 8.0')] diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php index 0d0ad4efbf..d8989e3e9d 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php @@ -3,11 +3,11 @@ namespace Bug13384c; function doFoo(): bool { - return false; + return returnsFalse(); } function doFoo2(): bool { - return true; + return returnsTrue(); } function doFoo3(): bool { @@ -20,22 +20,22 @@ function doFoo3(): bool { class Bug13384c { public function doBarPublic(): bool { - return false; + return returnsFalse(); } /** * @return false */ private function doBarPhpdocReturn(): bool { - return false; + return returnsFalse(); } private function doBar(): bool { - return false; + return returnsFalse(); } private function doBar2(): bool { - return true; + return returnsTrue(); } private function doBar3(): bool { @@ -46,25 +46,25 @@ private function doBar3(): bool { } private function doBarMixed() { - return true; + return returnsTrue(); } /** * @return bool */ private function doBarPhpdoc() { - return true; + return returnsTrue(); } } class Bug13384Static { private static function doBar(): bool { - return false; + return returnsFalse(); } private static function doBar2(): bool { - return true; + return returnsTrue(); } private static function doBar3(): bool { @@ -75,14 +75,14 @@ private static function doBar3(): bool { } private static function doBarMixed() { - return true; + return returnsTrue(); } /** * @return bool */ private static function doBarPhpdoc() { - return true; + return returnsTrue(); } } @@ -91,16 +91,38 @@ private static function doBarPhpdoc() { * @return bool */ function doFooPhpdoc() { - return true; + return returnsTrue(); } /** * @return bool */ function doFooPhpdoc2() { - return false; + return returnsFalse(); } function doFooMixed() { + return returnsTrue(); +} + +/** + * @return true + */ +function returnsTrue(): bool { + return true; +} + +/** + * @return false + */ +function returnsFalse(): bool { + return false; +} + +function returnsTrueNoPhpdoc(): bool { return true; } + +function returnsFalseNoPhpdoc(): bool { + return false; +}