Skip to content
Merged
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
9 changes: 9 additions & 0 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,15 @@ private function getConditionalSpecifiedTypes(
$ifType = $conditionalType->getIf();
$elseType = $conditionalType->getElse();

if (
(
$argumentExpr instanceof Node\Scalar
|| ($argumentExpr instanceof ConstFetch && in_array(strtolower($argumentExpr->name->toString()), ['true', 'false', 'null'], true))
) && ($ifType instanceof NeverType || $elseType instanceof NeverType)
) {
return null;
}

if ($leftType->isSuperTypeOf($ifType)->yes() && $rightType->isSuperTypeOf($elseType)->yes()) {
$context = $conditionalType->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue();
} elseif ($leftType->isSuperTypeOf($elseType)->yes() && $rightType->isSuperTypeOf($ifType)->yes()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1179,4 +1179,19 @@ public function testBug14177(): void
$this->analyse([__DIR__ . '/data/bug-14177.php'], []);
}

public function testBug13566(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-13566.php'], [
[
'Call to function is_numeric() with 123 will always evaluate to true.',
199,
],
[
'Call to function assertIsInt() with int will always evaluate to true.',
204,
],
]);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,18 @@ public function testBug12087b(): void
]);
}

public function testBug13566(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-13566.php'], []);
}

public function testBug10337(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-10337.php'], []);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ public function testBug12087b(): void
]);
}

public function testBug13566(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-13566.php'], []);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
62 changes: 62 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-10337.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types=1);

namespace Bug10337;

use function PHPStan\Testing\assertType;

class App
{
/**
* @return ($calledFromShutdownHandler is true ? void : never)
*/
public function callExit(bool $calledFromShutdownHandler = false): void
{
// run before shutdown code here

if (!$calledFromShutdownHandler) {
exit;
}
}

public function testOnlyVoid(): void
{
(new App())->callExit(true);
}

/**
* @return never
*/
public function testVoidAndNever(): void
{
$app = new App();
assertType('null', $app->callExit(true));
assertType('never', $app->callExit(false));
}

/**
* @return never
*/
public function testVoidAndNever2(): void
{
$app = new class() extends App {
};
assertType('null', $app->callExit(true));
assertType('never', $app->callExit(false));
}

/**
* @return never
*/
public function testVoidAndNever3(): void
{
$app = new class() extends App {
#[\Override]
public function callExit(bool $calledFromShutdownHandler = false): void
{
parent::callExit($calledFromShutdownHandler);
}
};
assertType('null', $app->callExit(true));
assertType('never', $app->callExit(false));
}
}
212 changes: 212 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-13566.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug13566;

use LogicException;

class ReturnViaBool
{
/** @return ($exit is true ? never : void) */
public static function notFound(bool $exit = true): void
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit) {
echo '404 Not Found';
exit;
}
}

public function test(): void
{
// send 404 header without exiting
self::notFound(false);
}
}

class ReturnViaInt
{
/** @return ($exit is 1 ? never : void) */
Comment thread
VincentLanglet marked this conversation as resolved.
public static function notFound(int $exit = 1): void
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit == 1) {
echo '404 Not Found';
exit;
}
}

public function test(): void
{
// send 404 header
self::notFound(0);
}
}

class ReturnViaString
{
/** @return ($exit is '1' ? never : void) */
public static function notFound(string $exit = '1'): void
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit == '1') {
echo '404 Not Found';
exit;
}
}

public function test(): void
{
// send 404 header
self::notFound('0');
}
}

class ReturnViaIntNonNative
{
/** @return ($exit is 1 ? never : void) */
public static function notFound(int $exit = 1)
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit == 1) {
echo '404 Not Found';
exit;
}
}

public function test(): void
{
// send 404 header
self::notFound(0);
}
}

class ReturnsMaybeNever
{
/** @return ($exit is true ? never : 1) */
public static function notFound(bool $exit = true)
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit) {
echo '404 Not Found';
exit;
}
return 1;
}

public function test(): void
{
// send 404 header
self::notFound(false);

}
}

class ReturnsMaybeVoid
{
/** @return ($exit is true ? void : 1) */
public static function notFound(bool $exit = true)
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit) {
echo '404 Not Found';
return;
}
return 1;
}

public function test(): void
{
// send 404 header
self::notFound(false);

}
}



class ReturnsWithInstanceMethod
{
/** @return ($exit is true ? never-return : void) */
public function notFound(bool $exit = true): void
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit) {
echo '404 Not Found';
exit;
}
}


public function test(): void
{
// send 404 header
$this->notFound(false);

}
}

/** @return ($exit is true ? never-return : void) */
function notFound(bool $exit = true): void
{
header('HTTP/1.1 404 Not Found', true, 404);

if ($exit) {
echo '404 Not Found';
exit;
}
}

function test(): void
{
// send 404 header
notFound(false);
}


class ReturnViaUnion
{
/**
* @return ($exit is int ? void : never)
*/
public static function notFound(int|string $exit = 'hello world'): void
{
header('HTTP/1.1 404 Not Found', true, 404);

if (!is_int($exit)) {
echo '404 Not Found';
exit;
}
}

public function test(): void
{
// send 404 header
self::notFound(0);
}
}


$x = 123;
if (is_numeric($x)) {
}

function doFoo($mixed) {
assertIsInt($mixed);
assertIsInt($mixed);
}

/** @phpstan-assert int $i */
function assertIsInt($i):void {
if (!is_int($i)) {
throw new \LogicException();
}
}
Loading