Skip to content

Commit d6b6f77

Browse files
phpstan-botstaabmVincentLanglet
authored
Fix phpstan/phpstan#13566: False positive staticMethod.impossibleType
Co-authored-by: staabm <120441+staabm@users.noreply.github.com> Co-authored-by: Markus Staab <markus.staab@redaxo.de> Co-authored-by: Markus Staab <maggus.staab@googlemail.com> Co-authored-by: Vincent Langlet <VincentLanglet@users.noreply.github.com>
1 parent 4cc61e7 commit d6b6f77

File tree

6 files changed

+316
-0
lines changed

6 files changed

+316
-0
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,15 @@ private function getConditionalSpecifiedTypes(
15841584
$ifType = $conditionalType->getIf();
15851585
$elseType = $conditionalType->getElse();
15861586

1587+
if (
1588+
(
1589+
$argumentExpr instanceof Node\Scalar
1590+
|| ($argumentExpr instanceof ConstFetch && in_array(strtolower($argumentExpr->name->toString()), ['true', 'false', 'null'], true))
1591+
) && ($ifType instanceof NeverType || $elseType instanceof NeverType)
1592+
) {
1593+
return null;
1594+
}
1595+
15871596
if ($leftType->isSuperTypeOf($ifType)->yes() && $rightType->isSuperTypeOf($elseType)->yes()) {
15881597
$context = $conditionalType->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue();
15891598
} elseif ($leftType->isSuperTypeOf($elseType)->yes() && $rightType->isSuperTypeOf($ifType)->yes()) {

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,4 +1179,19 @@ public function testBug14177(): void
11791179
$this->analyse([__DIR__ . '/data/bug-14177.php'], []);
11801180
}
11811181

1182+
public function testBug13566(): void
1183+
{
1184+
$this->treatPhpDocTypesAsCertain = true;
1185+
$this->analyse([__DIR__ . '/data/bug-13566.php'], [
1186+
[
1187+
'Call to function is_numeric() with 123 will always evaluate to true.',
1188+
199,
1189+
],
1190+
[
1191+
'Call to function assertIsInt() with int will always evaluate to true.',
1192+
204,
1193+
],
1194+
]);
1195+
}
1196+
11821197
}

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,18 @@ public function testBug12087b(): void
290290
]);
291291
}
292292

293+
public function testBug13566(): void
294+
{
295+
$this->treatPhpDocTypesAsCertain = true;
296+
$this->analyse([__DIR__ . '/data/bug-13566.php'], []);
297+
}
298+
299+
public function testBug10337(): void
300+
{
301+
$this->treatPhpDocTypesAsCertain = true;
302+
$this->analyse([__DIR__ . '/data/bug-10337.php'], []);
303+
}
304+
293305
public static function getAdditionalConfigFiles(): array
294306
{
295307
return [

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ public function testBug12087b(): void
161161
]);
162162
}
163163

164+
public function testBug13566(): void
165+
{
166+
$this->treatPhpDocTypesAsCertain = true;
167+
$this->analyse([__DIR__ . '/data/bug-13566.php'], []);
168+
}
169+
164170
public static function getAdditionalConfigFiles(): array
165171
{
166172
return [
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug10337;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class App
8+
{
9+
/**
10+
* @return ($calledFromShutdownHandler is true ? void : never)
11+
*/
12+
public function callExit(bool $calledFromShutdownHandler = false): void
13+
{
14+
// run before shutdown code here
15+
16+
if (!$calledFromShutdownHandler) {
17+
exit;
18+
}
19+
}
20+
21+
public function testOnlyVoid(): void
22+
{
23+
(new App())->callExit(true);
24+
}
25+
26+
/**
27+
* @return never
28+
*/
29+
public function testVoidAndNever(): void
30+
{
31+
$app = new App();
32+
assertType('null', $app->callExit(true));
33+
assertType('never', $app->callExit(false));
34+
}
35+
36+
/**
37+
* @return never
38+
*/
39+
public function testVoidAndNever2(): void
40+
{
41+
$app = new class() extends App {
42+
};
43+
assertType('null', $app->callExit(true));
44+
assertType('never', $app->callExit(false));
45+
}
46+
47+
/**
48+
* @return never
49+
*/
50+
public function testVoidAndNever3(): void
51+
{
52+
$app = new class() extends App {
53+
#[\Override]
54+
public function callExit(bool $calledFromShutdownHandler = false): void
55+
{
56+
parent::callExit($calledFromShutdownHandler);
57+
}
58+
};
59+
assertType('null', $app->callExit(true));
60+
assertType('never', $app->callExit(false));
61+
}
62+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug13566;
6+
7+
use LogicException;
8+
9+
class ReturnViaBool
10+
{
11+
/** @return ($exit is true ? never : void) */
12+
public static function notFound(bool $exit = true): void
13+
{
14+
header('HTTP/1.1 404 Not Found', true, 404);
15+
16+
if ($exit) {
17+
echo '404 Not Found';
18+
exit;
19+
}
20+
}
21+
22+
public function test(): void
23+
{
24+
// send 404 header without exiting
25+
self::notFound(false);
26+
}
27+
}
28+
29+
class ReturnViaInt
30+
{
31+
/** @return ($exit is 1 ? never : void) */
32+
public static function notFound(int $exit = 1): void
33+
{
34+
header('HTTP/1.1 404 Not Found', true, 404);
35+
36+
if ($exit == 1) {
37+
echo '404 Not Found';
38+
exit;
39+
}
40+
}
41+
42+
public function test(): void
43+
{
44+
// send 404 header
45+
self::notFound(0);
46+
}
47+
}
48+
49+
class ReturnViaString
50+
{
51+
/** @return ($exit is '1' ? never : void) */
52+
public static function notFound(string $exit = '1'): void
53+
{
54+
header('HTTP/1.1 404 Not Found', true, 404);
55+
56+
if ($exit == '1') {
57+
echo '404 Not Found';
58+
exit;
59+
}
60+
}
61+
62+
public function test(): void
63+
{
64+
// send 404 header
65+
self::notFound('0');
66+
}
67+
}
68+
69+
class ReturnViaIntNonNative
70+
{
71+
/** @return ($exit is 1 ? never : void) */
72+
public static function notFound(int $exit = 1)
73+
{
74+
header('HTTP/1.1 404 Not Found', true, 404);
75+
76+
if ($exit == 1) {
77+
echo '404 Not Found';
78+
exit;
79+
}
80+
}
81+
82+
public function test(): void
83+
{
84+
// send 404 header
85+
self::notFound(0);
86+
}
87+
}
88+
89+
class ReturnsMaybeNever
90+
{
91+
/** @return ($exit is true ? never : 1) */
92+
public static function notFound(bool $exit = true)
93+
{
94+
header('HTTP/1.1 404 Not Found', true, 404);
95+
96+
if ($exit) {
97+
echo '404 Not Found';
98+
exit;
99+
}
100+
return 1;
101+
}
102+
103+
public function test(): void
104+
{
105+
// send 404 header
106+
self::notFound(false);
107+
108+
}
109+
}
110+
111+
class ReturnsMaybeVoid
112+
{
113+
/** @return ($exit is true ? void : 1) */
114+
public static function notFound(bool $exit = true)
115+
{
116+
header('HTTP/1.1 404 Not Found', true, 404);
117+
118+
if ($exit) {
119+
echo '404 Not Found';
120+
return;
121+
}
122+
return 1;
123+
}
124+
125+
public function test(): void
126+
{
127+
// send 404 header
128+
self::notFound(false);
129+
130+
}
131+
}
132+
133+
134+
135+
class ReturnsWithInstanceMethod
136+
{
137+
/** @return ($exit is true ? never-return : void) */
138+
public function notFound(bool $exit = true): void
139+
{
140+
header('HTTP/1.1 404 Not Found', true, 404);
141+
142+
if ($exit) {
143+
echo '404 Not Found';
144+
exit;
145+
}
146+
}
147+
148+
149+
public function test(): void
150+
{
151+
// send 404 header
152+
$this->notFound(false);
153+
154+
}
155+
}
156+
157+
/** @return ($exit is true ? never-return : void) */
158+
function notFound(bool $exit = true): void
159+
{
160+
header('HTTP/1.1 404 Not Found', true, 404);
161+
162+
if ($exit) {
163+
echo '404 Not Found';
164+
exit;
165+
}
166+
}
167+
168+
function test(): void
169+
{
170+
// send 404 header
171+
notFound(false);
172+
}
173+
174+
175+
class ReturnViaUnion
176+
{
177+
/**
178+
* @return ($exit is int ? void : never)
179+
*/
180+
public static function notFound(int|string $exit = 'hello world'): void
181+
{
182+
header('HTTP/1.1 404 Not Found', true, 404);
183+
184+
if (!is_int($exit)) {
185+
echo '404 Not Found';
186+
exit;
187+
}
188+
}
189+
190+
public function test(): void
191+
{
192+
// send 404 header
193+
self::notFound(0);
194+
}
195+
}
196+
197+
198+
$x = 123;
199+
if (is_numeric($x)) {
200+
}
201+
202+
function doFoo($mixed) {
203+
assertIsInt($mixed);
204+
assertIsInt($mixed);
205+
}
206+
207+
/** @phpstan-assert int $i */
208+
function assertIsInt($i):void {
209+
if (!is_int($i)) {
210+
throw new \LogicException();
211+
}
212+
}

0 commit comments

Comments
 (0)