Skip to content

Commit c7e6244

Browse files
rvanvelzenondrejmirtes
authored andcommitted
Fix NAN not accepting NAN
1 parent 0b6d92d commit c7e6244

File tree

6 files changed

+119
-32
lines changed

6 files changed

+119
-32
lines changed

src/Type/Constant/ConstantFloatType.php

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@
55
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
66
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
77
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8-
use PHPStan\TrinaryLogic;
9-
use PHPStan\Type\CompoundType;
108
use PHPStan\Type\ConstantScalarType;
119
use PHPStan\Type\FloatType;
1210
use PHPStan\Type\GeneralizePrecision;
1311
use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait;
1412
use PHPStan\Type\Traits\ConstantScalarTypeTrait;
1513
use PHPStan\Type\Type;
1614
use PHPStan\Type\VerbosityLevel;
17-
use function abs;
1815
use function ini_get;
1916
use function ini_set;
2017
use function is_finite;
18+
use function is_nan;
2119
use function str_contains;
22-
use const PHP_FLOAT_EPSILON;
2320

2421
/** @api */
2522
class ConstantFloatType extends FloatType implements ConstantScalarType
@@ -42,7 +39,7 @@ public function getValue(): float
4239

4340
public function equals(Type $type): bool
4441
{
45-
return $type instanceof self && abs($this->value - $type->value) < PHP_FLOAT_EPSILON;
42+
return $type instanceof self && ($this->value === $type->value || is_nan($this->value) && is_nan($type->value));
4643
}
4744

4845
private function castFloatToString(float $value): string
@@ -69,31 +66,6 @@ public function describe(VerbosityLevel $level): string
6966
);
7067
}
7168

72-
public function isSuperTypeOf(Type $type): TrinaryLogic
73-
{
74-
if ($type instanceof self) {
75-
if (!$this->equals($type)) {
76-
if (abs($this->value - $type->value) < PHP_FLOAT_EPSILON) {
77-
return TrinaryLogic::createMaybe();
78-
}
79-
80-
return TrinaryLogic::createNo();
81-
}
82-
83-
return TrinaryLogic::createYes();
84-
}
85-
86-
if ($type instanceof parent) {
87-
return TrinaryLogic::createMaybe();
88-
}
89-
90-
if ($type instanceof CompoundType) {
91-
return $type->isSubTypeOf($this);
92-
}
93-
94-
return TrinaryLogic::createNo();
95-
}
96-
9769
public function toString(): Type
9870
{
9971
return new ConstantStringType((string) $this->value);

src/Type/Traits/ConstantScalarTypeTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic
2424
public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
2525
{
2626
if ($type instanceof self) {
27-
return AcceptsResult::createFromBoolean($this->value === $type->value);
27+
return AcceptsResult::createFromBoolean($this->equals($type));
2828
}
2929

3030
if ($type instanceof CompoundType) {
@@ -37,7 +37,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
3737
public function isSuperTypeOf(Type $type): TrinaryLogic
3838
{
3939
if ($type instanceof self) {
40-
return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo();
40+
return TrinaryLogic::createFromBoolean($this->equals($type));
4141
}
4242

4343
if ($type instanceof parent) {

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,4 +1646,9 @@ public function testArgon2PasswordHash(): void
16461646
$this->analyse([__DIR__ . '/data/argon2id-password-hash.php'], []);
16471647
}
16481648

1649+
public function testBug10297(): void
1650+
{
1651+
$this->analyse([__DIR__ . '/data/bug-10297.php'], []);
1652+
}
1653+
16491654
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug10297;
4+
5+
use Generator;
6+
use TypeError;
7+
use UnexpectedValueException;
8+
9+
/**
10+
* @template K
11+
* @template T
12+
* @template L
13+
* @template U
14+
*
15+
* @param iterable<K, T> $stream
16+
* @param callable(T, K): iterable<L, U> $fn
17+
*
18+
* @return Generator<L, U>
19+
*/
20+
function scollect(iterable $stream, callable $fn): Generator
21+
{
22+
foreach ($stream as $key => $value) {
23+
yield from $fn($value, $key);
24+
}
25+
}
26+
27+
/**
28+
* @template K of array-key
29+
* @template T
30+
* @template L of array-key
31+
* @template U
32+
*
33+
* @param array<K, T> $array
34+
* @param callable(T, K): iterable<L, U> $fn
35+
*
36+
* @return array<L, U>
37+
*/
38+
function collectWithKeys(array $array, callable $fn): array
39+
{
40+
$map = [];
41+
$counter = 0;
42+
43+
try {
44+
foreach (scollect($array, $fn) as $key => $value) {
45+
$map[$key] = $value;
46+
++$counter;
47+
}
48+
} catch (TypeError) {
49+
throw new UnexpectedValueException('The key yielded in the callable is not compatible with the type "array-key".');
50+
}
51+
52+
if ($counter !== count($map)) {
53+
throw new UnexpectedValueException(
54+
'Data loss occurred because of duplicated keys. Use `collect()` if you do not care about ' .
55+
'the yielded keys, or use `scollect()` if you need to support duplicated keys (as arrays cannot).',
56+
);
57+
}
58+
59+
return $map;
60+
}
61+
62+
class SomeUnitTest
63+
{
64+
/**
65+
* @return iterable<mixed>
66+
*/
67+
public static function someProvider(): iterable
68+
{
69+
$unsupportedTypes = [
70+
// this one does not work:
71+
'Not a Number' => NAN,
72+
// these work:
73+
'Infinity' => INF,
74+
stdClass::class => new stdClass(),
75+
self::class => self::class,
76+
'hello there' => 'hello there',
77+
'array' => [[42]],
78+
];
79+
80+
yield from collectWithKeys($unsupportedTypes, static function (mixed $value, string $type): iterable {
81+
$error = sprintf('Some %s error message', $type);
82+
83+
yield sprintf('"%s" something something', $type) => [$value, [$error, $error, $error]];
84+
});
85+
}
86+
}

tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,9 @@ public function testDefaultValueForPromotedProperty(): void
7373
]);
7474
}
7575

76+
public function testBug10956(): void
77+
{
78+
$this->analyse([__DIR__ . '/data/bug-10956.php'], []);
79+
}
80+
7681
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug10956;
4+
5+
class HelloWorld
6+
{
7+
const DEFAULT_VALUE = [NAN]; // unique value, distinct from anything, but equal to itself
8+
9+
/**
10+
* @param int|self::DEFAULT_VALUE $value
11+
*/
12+
public function test (mixed $value = self::DEFAULT_VALUE) {
13+
if ($value === self::DEFAULT_VALUE) {
14+
echo 'default';
15+
} else {
16+
echo (int) $value;
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)