Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Rules/TooWideTypehints/TooWideTypeCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);

Expand Down
26 changes: 9 additions & 17 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')]
Expand Down
50 changes: 36 additions & 14 deletions tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
namespace Bug13384c;

function doFoo(): bool {
return false;
return returnsFalse();
}

function doFoo2(): bool {
return true;
return returnsTrue();
}

function doFoo3(): bool {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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();
}

}
Expand All @@ -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;
}
Loading