Skip to content

Fix phpstan/phpstan#14394: NAN ==/=== anything is always evaluated as false#5332

Closed
phpstan-bot wants to merge 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-xlw3xzo
Closed

Fix phpstan/phpstan#14394: NAN ==/=== anything is always evaluated as false#5332
phpstan-bot wants to merge 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-xlw3xzo

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

In PHP, NAN == anything and NAN === anything always evaluate to false, including NAN === NAN. This also applies to arrays containing NAN values. PHPStan was not detecting these always-false comparisons when one side was a non-constant type (e.g. float === NAN or list<mixed> === [NAN]).

Changes

  • Added containsNan() private helper method in src/Reflection/InitializerExprTypeResolver.php that recursively checks if a type (scalar or constant array) contains a NAN value
  • Added NAN checks at the top of resolveIdenticalType() and resolveEqualType() so that any comparison involving NAN returns ConstantBooleanType(false)
  • Updated tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php bug-11054 test expectations — [NAN] === mixed is now correctly reported as always false (this matches PHP runtime behavior: [NAN] === [NAN] is false)
  • New regression test in tests/PHPStan/Rules/Comparison/data/bug-14394.php covering float == NAN, float === NAN, list<mixed> == [NAN], and list<mixed> === [NAN]

Root cause

resolveIdenticalType() had no special handling for NAN. When comparing e.g. float === NAN:

  1. Neither side is NeverType — skip
  2. Both aren't ConstantScalarType (only NAN side is) — skip
  3. NAN's getFiniteTypes() returns [] — finite type check skipped
  4. FloatType->isSuperTypeOf(ConstantFloatType(NAN)) returns yes — different-types check skipped
  5. Falls through to return generic BooleanType() instead of ConstantBooleanType(false)

The fix adds an early return for NAN at the top of both comparison resolution methods, before any other logic runs.

Test

Added rule tests for both StrictComparisonOfDifferentTypesRule and ConstantLooseComparisonRule with test data in tests/PHPStan/Rules/Comparison/data/bug-14394.php covering all four comparison scenarios from the issue.

Fixes phpstan/phpstan#14394

… 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
return true;
}

foreach ($type->getConstantArrays() as $constantArray) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the array is array{Nan}|array{1} the egality check might be true.

We need all array to contain Nan on a non optional

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All done — both background tasks (make tests and make phpstan) completed successfully, and the fix has been pushed.

When a type is a union of constant arrays (e.g. array{NAN}|array{1}),
containsNan should only return true if ALL variants contain NAN, not
just any of them. Also skip optional keys since absent optional entries
mean the array might not actually contain NAN.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
}

$constantArrays = $type->getConstantArrays();
if ($constantArrays !== [] && $type->isConstantArray()->yes()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the isConstantArray check

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background tests task confirmed passing too. Both background tasks from earlier have now completed successfully — all clean.

$constantArrays = $type->getConstantArrays();
if ($constantArrays !== [] && $type->isConstantArray()->yes()) {
foreach ($constantArrays as $constantArray) {
if (!$this->constantArrayContainsNan($constantArray)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid a dedicated method constantArrayContainsNan and put the implementation here

…tArrayContainsNan

Address review feedback from VincentLanglet:
- Remove redundant isConstantArray()->yes() check
- Inline constantArrayContainsNan into containsNan to avoid a dedicated method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add test for

$a = NAN;
$a == $a;
[$a] == [$a];


$constantArrays = $type->getConstantArrays();
if ($constantArrays !== []) {
foreach ($constantArrays as $constantArray) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to rely on the result of the expression in_array(NAN, $type, true) ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background make tests also confirmed passing. All done — both test suite and static analysis pass, changes are committed and pushed.

… 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>
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-xlw3xzo branch March 30, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants