Skip to content
Closed
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
5 changes: 4 additions & 1 deletion src/Analyser/ExprHandler/BinaryOpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type
&& is_string($expr->right->name)
&& $expr->left->name === $expr->right->name
) {
return new ConstantBooleanType(true);
$varType = $scope->getType($expr->left);
if (!$this->initializerExprTypeResolver->containsNan($varType)) {
return new ConstantBooleanType(true);
}
}

$leftType = $scope->getType($expr->left);
Expand Down
5 changes: 4 additions & 1 deletion src/Analyser/RicherScopeGetTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public function getIdenticalResult(Scope $scope, Identical $expr): TypeResult
&& is_string($expr->right->name)
&& $expr->left->name === $expr->right->name
) {
return new TypeResult(new ConstantBooleanType(true), []);
$varType = $scope->getType($expr->left);
if (!$this->initializerExprTypeResolver->containsNan($varType)) {
return new TypeResult(new ConstantBooleanType(true), []);
}
}

$leftType = $scope->getType($expr->left);
Expand Down
40 changes: 40 additions & 0 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
use function is_finite;
use function is_float;
use function is_int;
use function is_nan;
use function is_numeric;
use function is_string;
use function max;
Expand Down Expand Up @@ -1944,6 +1945,10 @@ public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResul
return new TypeResult(new ConstantBooleanType(false), []);
}

if ($this->containsNan($leftType) || $this->containsNan($rightType)) {
return new TypeResult(new ConstantBooleanType(false), []);
}

if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) {
return new TypeResult(new ConstantBooleanType($leftType->getValue() === $rightType->getValue()), []);
}
Expand Down Expand Up @@ -1972,6 +1977,10 @@ public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResul
*/
public function resolveEqualType(Type $leftType, Type $rightType): TypeResult
{
if ($this->containsNan($leftType) || $this->containsNan($rightType)) {
return new TypeResult(new ConstantBooleanType(false), []);
}

if (
($leftType->isEnum()->yes() && $rightType->isTrue()->no())
|| ($rightType->isEnum()->yes() && $leftType->isTrue()->no())
Expand Down Expand Up @@ -2061,6 +2070,37 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType,
return new TypeResult($resultType->toBoolean(), []);
}

public function containsNan(Type $type): bool
{
if ($type instanceof ConstantFloatType && is_nan($type->getValue())) {
return true;
}

$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.

$optionalKeys = $constantArray->getOptionalKeys();
$hasNan = false;
foreach ($constantArray->getValueTypes() as $index => $valueType) {
if (in_array($index, $optionalKeys, true)) {
continue;
}
if ($this->containsNan($valueType)) {
$hasNan = true;
break;
}
}
if (!$hasNan) {
return false;
}
}

return true;
}

return false;
}

/**
* @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr
*/
Expand Down
23 changes: 23 additions & 0 deletions tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,27 @@ public function testBug13098(): void
$this->analyse([__DIR__ . '/data/bug-13098.php'], []);
}

public function testBug14394(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-14394.php'], [
[
'Loose comparison using == between float and NAN will always evaluate to false.',
8,
],
[
'Loose comparison using == between list<mixed> and array{NAN} will always evaluate to false.',
10,
],
[
'Loose comparison using == between NAN and NAN will always evaluate to false.',
16,
],
[
'Loose comparison using == between array{NAN} and array{NAN} will always evaluate to false.',
18,
],
]);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1164,11 +1164,45 @@ public function testPossiblyImpureTip(): void
public function testBug11054(): void
{
$this->analyse([__DIR__ . '/data/bug-11054.php'], [
[
'Strict comparison using === between mixed and array{NAN} will always evaluate to false.',
24,
],
[
'Strict comparison using === between mixed and array{NAN} will always evaluate to false.',
28,
],
[
'Strict comparison using === between mixed and array{INF} will always evaluate to false.',
47,
'Type array{INF} has already been eliminated from mixed.',
],
[
'Strict comparison using === between mixed and array{NAN} will always evaluate to false.',
55,
],
]);
}

public function testBug14394(): void
{
$this->analyse([__DIR__ . '/data/bug-14394.php'], [
[
'Strict comparison using === between float and NAN will always evaluate to false.',
9,
],
[
'Strict comparison using === between list<mixed> and array{NAN} will always evaluate to false.',
11,
],
[
'Strict comparison using === between NAN and NAN will always evaluate to false.',
17,
],
[
'Strict comparison using === between array{NAN} and array{NAN} will always evaluate to false.',
19,
],
]);
}

Expand Down
27 changes: 27 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-14394.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace Bug14394;

class Cl {
/** @param list<mixed> $v2 */
public static function test(float $v1, array $v2): void {
if ($v1 == NAN) { echo "never reached\n"; }
if ($v1 === NAN) { echo "never reached\n"; }
if ($v2 == [NAN]) { echo "never reached\n"; }
if ($v2 === [NAN]) { echo "never reached\n"; }
}

public static function testSameVariable(): void {
$a = NAN;
if ($a == $a) { echo "never reached\n"; }
if ($a === $a) { echo "never reached\n"; }
if ([$a] == [$a]) { echo "never reached\n"; }
if ([$a] === [$a]) { echo "never reached\n"; }
}

/** @param array{NAN}|array{1} $v */
public static function testUnionArrayNotAlwaysFalse(array $v): void {
if ($v === [1]) { echo "maybe reached\n"; }
if ($v == [1]) { echo "maybe reached\n"; }
}
}
Loading