Skip to content

Commit 2302b10

Browse files
phpstan-botclaude
andcommitted
Fix NAN same-variable comparison: $a === $a where $a is NAN should be false
The same-variable short-circuit in RicherScopeGetTypeHelper and BinaryOpHandler assumed $a === $a and $a == $a are always true, but this is incorrect when $a is NAN (NAN !== NAN in PHP). Added NAN check before the short-circuit, and made containsNan public for reuse. Added test cases for $a = NAN; $a == $a; [$a] == [$a] as requested. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f0cb81a commit 2302b10

6 files changed

Lines changed: 33 additions & 3 deletions

File tree

src/Analyser/ExprHandler/BinaryOpHandler.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,10 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type
109109
&& is_string($expr->right->name)
110110
&& $expr->left->name === $expr->right->name
111111
) {
112-
return new ConstantBooleanType(true);
112+
$varType = $scope->getType($expr->left);
113+
if (!$this->initializerExprTypeResolver->containsNan($varType)) {
114+
return new ConstantBooleanType(true);
115+
}
113116
}
114117

115118
$leftType = $scope->getType($expr->left);

src/Analyser/RicherScopeGetTypeHelper.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ public function getIdenticalResult(Scope $scope, Identical $expr): TypeResult
3636
&& is_string($expr->right->name)
3737
&& $expr->left->name === $expr->right->name
3838
) {
39-
return new TypeResult(new ConstantBooleanType(true), []);
39+
$varType = $scope->getType($expr->left);
40+
if (!$this->initializerExprTypeResolver->containsNan($varType)) {
41+
return new TypeResult(new ConstantBooleanType(true), []);
42+
}
4043
}
4144

4245
$leftType = $scope->getType($expr->left);

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2070,7 +2070,7 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType,
20702070
return new TypeResult($resultType->toBoolean(), []);
20712071
}
20722072

2073-
private function containsNan(Type $type): bool
2073+
public function containsNan(Type $type): bool
20742074
{
20752075
if ($type instanceof ConstantFloatType && is_nan($type->getValue())) {
20762076
return true;

tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ public function testBug14394(): void
260260
'Loose comparison using == between list<mixed> and array{NAN} will always evaluate to false.',
261261
10,
262262
],
263+
[
264+
'Loose comparison using == between NAN and NAN will always evaluate to false.',
265+
16,
266+
],
267+
[
268+
'Loose comparison using == between array{NAN} and array{NAN} will always evaluate to false.',
269+
18,
270+
],
263271
]);
264272
}
265273

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,14 @@ public function testBug14394(): void
11951195
'Strict comparison using === between list<mixed> and array{NAN} will always evaluate to false.',
11961196
11,
11971197
],
1198+
[
1199+
'Strict comparison using === between NAN and NAN will always evaluate to false.',
1200+
17,
1201+
],
1202+
[
1203+
'Strict comparison using === between array{NAN} and array{NAN} will always evaluate to false.',
1204+
19,
1205+
],
11981206
]);
11991207
}
12001208

tests/PHPStan/Rules/Comparison/data/bug-14394.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ public static function test(float $v1, array $v2): void {
1111
if ($v2 === [NAN]) { echo "never reached\n"; }
1212
}
1313

14+
public static function testSameVariable(): void {
15+
$a = NAN;
16+
if ($a == $a) { echo "never reached\n"; }
17+
if ($a === $a) { echo "never reached\n"; }
18+
if ([$a] == [$a]) { echo "never reached\n"; }
19+
if ([$a] === [$a]) { echo "never reached\n"; }
20+
}
21+
1422
/** @param array{NAN}|array{1} $v */
1523
public static function testUnionArrayNotAlwaysFalse(array $v): void {
1624
if ($v === [1]) { echo "maybe reached\n"; }

0 commit comments

Comments
 (0)