Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
44 changes: 44 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 @@
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 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,41 @@
return new TypeResult($resultType->toBoolean(), []);
}

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

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

Check warning on line 2080 in src/Reflection/InitializerExprTypeResolver.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } $constantArrays = $type->getConstantArrays(); - if ($constantArrays !== [] && $type->isConstantArray()->yes()) { + if ($constantArrays !== [] && !$type->isConstantArray()->no()) { foreach ($constantArrays as $constantArray) { if (!$this->constantArrayContainsNan($constantArray)) { return false;

Check warning on line 2080 in src/Reflection/InitializerExprTypeResolver.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } $constantArrays = $type->getConstantArrays(); - if ($constantArrays !== [] && $type->isConstantArray()->yes()) { + if ($constantArrays !== [] && !$type->isConstantArray()->no()) { foreach ($constantArrays as $constantArray) { if (!$this->constantArrayContainsNan($constantArray)) { return false;
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.

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.

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

return false;
}
}

return true;
}

return false;
}

private function constantArrayContainsNan(ConstantArrayType $constantArray): bool
{
$optionalKeys = $constantArray->getOptionalKeys();
foreach ($constantArray->getValueTypes() as $index => $valueType) {
if (in_array($index, $optionalKeys, true)) {
continue;
}
if ($this->containsNan($valueType)) {
return true;
}
}

return false;
}

/**
* @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr
*/
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,19 @@ 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,
],
]);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1164,11 +1164,37 @@ 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,
],
]);
}

Expand Down
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-14394.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?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"; }
}

/** @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