Skip to content

Commit 869b3e4

Browse files
VincentLangletphpstan-bot
authored andcommitted
Fix phpstan/phpstan#14394: NAN ==/=== anything is always evaluated as false
- Added containsNan() helper in InitializerExprTypeResolver to recursively check if a type (including constant arrays) contains NAN - resolveIdenticalType() and resolveEqualType() now return always-false when either operand contains NAN - Updated bug-11054 test expectations: [NAN] === mixed is now correctly reported as always false (matching PHP runtime behavior) - New regression test in tests/PHPStan/Rules/Comparison/data/bug-14394.php
1 parent 4c6ef6e commit 869b3e4

4 files changed

Lines changed: 80 additions & 0 deletions

File tree

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
use function is_finite;
115115
use function is_float;
116116
use function is_int;
117+
use function is_nan;
117118
use function is_numeric;
118119
use function is_string;
119120
use function max;
@@ -1944,6 +1945,10 @@ public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResul
19441945
return new TypeResult(new ConstantBooleanType(false), []);
19451946
}
19461947

1948+
if ($this->containsNan($leftType) || $this->containsNan($rightType)) {
1949+
return new TypeResult(new ConstantBooleanType(false), []);
1950+
}
1951+
19471952
if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) {
19481953
return new TypeResult(new ConstantBooleanType($leftType->getValue() === $rightType->getValue()), []);
19491954
}
@@ -1972,6 +1977,10 @@ public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResul
19721977
*/
19731978
public function resolveEqualType(Type $leftType, Type $rightType): TypeResult
19741979
{
1980+
if ($this->containsNan($leftType) || $this->containsNan($rightType)) {
1981+
return new TypeResult(new ConstantBooleanType(false), []);
1982+
}
1983+
19751984
if (
19761985
($leftType->isEnum()->yes() && $rightType->isTrue()->no())
19771986
|| ($rightType->isEnum()->yes() && $leftType->isTrue()->no())
@@ -2061,6 +2070,23 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType,
20612070
return new TypeResult($resultType->toBoolean(), []);
20622071
}
20632072

2073+
private function containsNan(Type $type): bool
2074+
{
2075+
if ($type instanceof ConstantFloatType && is_nan($type->getValue())) {
2076+
return true;
2077+
}
2078+
2079+
foreach ($type->getConstantArrays() as $constantArray) {
2080+
foreach ($constantArray->getValueTypes() as $valueType) {
2081+
if ($this->containsNan($valueType)) {
2082+
return true;
2083+
}
2084+
}
2085+
}
2086+
2087+
return false;
2088+
}
2089+
20642090
/**
20652091
* @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr
20662092
*/

tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,19 @@ public function testBug13098(): void
248248
$this->analyse([__DIR__ . '/data/bug-13098.php'], []);
249249
}
250250

251+
public function testBug14394(): void
252+
{
253+
$this->treatPhpDocTypesAsCertain = true;
254+
$this->analyse([__DIR__ . '/data/bug-14394.php'], [
255+
[
256+
'Loose comparison using == between float and NAN will always evaluate to false.',
257+
8,
258+
],
259+
[
260+
'Loose comparison using == between list<mixed> and array{NAN} will always evaluate to false.',
261+
10,
262+
],
263+
]);
264+
}
265+
251266
}

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,11 +1164,37 @@ public function testPossiblyImpureTip(): void
11641164
public function testBug11054(): void
11651165
{
11661166
$this->analyse([__DIR__ . '/data/bug-11054.php'], [
1167+
[
1168+
'Strict comparison using === between mixed and array{NAN} will always evaluate to false.',
1169+
24,
1170+
],
1171+
[
1172+
'Strict comparison using === between mixed and array{NAN} will always evaluate to false.',
1173+
28,
1174+
],
11671175
[
11681176
'Strict comparison using === between mixed and array{INF} will always evaluate to false.',
11691177
47,
11701178
'Type array{INF} has already been eliminated from mixed.',
11711179
],
1180+
[
1181+
'Strict comparison using === between mixed and array{NAN} will always evaluate to false.',
1182+
55,
1183+
],
1184+
]);
1185+
}
1186+
1187+
public function testBug14394(): void
1188+
{
1189+
$this->analyse([__DIR__ . '/data/bug-14394.php'], [
1190+
[
1191+
'Strict comparison using === between float and NAN will always evaluate to false.',
1192+
9,
1193+
],
1194+
[
1195+
'Strict comparison using === between list<mixed> and array{NAN} will always evaluate to false.',
1196+
11,
1197+
],
11721198
]);
11731199
}
11741200

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14394;
4+
5+
class Cl {
6+
/** @param list<mixed> $v2 */
7+
public static function test(float $v1, array $v2): void {
8+
if ($v1 == NAN) { echo "never reached\n"; }
9+
if ($v1 === NAN) { echo "never reached\n"; }
10+
if ($v2 == [NAN]) { echo "never reached\n"; }
11+
if ($v2 === [NAN]) { echo "never reached\n"; }
12+
}
13+
}

0 commit comments

Comments
 (0)