Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/1.10.x' into 1.11.x
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 3, 2024
2 parents e1a61a6 + c7e6244 commit 4d162fa
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 32 deletions.
32 changes: 2 additions & 30 deletions src/Type/Constant/ConstantFloatType.php
Expand Up @@ -5,21 +5,18 @@
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\TrinaryLogic;
use PHPStan\Type\CompoundType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\FloatType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait;
use PHPStan\Type\Traits\ConstantScalarTypeTrait;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use function abs;
use function ini_get;
use function ini_set;
use function is_finite;
use function is_nan;
use function str_contains;
use const PHP_FLOAT_EPSILON;

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

public function equals(Type $type): bool
{
return $type instanceof self && abs($this->value - $type->value) < PHP_FLOAT_EPSILON;
return $type instanceof self && ($this->value === $type->value || is_nan($this->value) && is_nan($type->value));
}

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

public function isSuperTypeOf(Type $type): TrinaryLogic
{
if ($type instanceof self) {
if (!$this->equals($type)) {
if (abs($this->value - $type->value) < PHP_FLOAT_EPSILON) {
return TrinaryLogic::createMaybe();
}

return TrinaryLogic::createNo();
}

return TrinaryLogic::createYes();
}

if ($type instanceof parent) {
return TrinaryLogic::createMaybe();
}

if ($type instanceof CompoundType) {
return $type->isSubTypeOf($this);
}

return TrinaryLogic::createNo();
}

public function toString(): Type
{
return new ConstantStringType((string) $this->value);
Expand Down
4 changes: 2 additions & 2 deletions src/Type/Traits/ConstantScalarTypeTrait.php
Expand Up @@ -24,7 +24,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic
public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
{
if ($type instanceof self) {
return AcceptsResult::createFromBoolean($this->value === $type->value);
return AcceptsResult::createFromBoolean($this->equals($type));
}

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

if ($type instanceof parent) {
Expand Down
Expand Up @@ -1664,4 +1664,9 @@ public function testParamClosureThis(): void
]);
}

public function testBug10297(): void
{
$this->analyse([__DIR__ . '/data/bug-10297.php'], []);
}

}
86 changes: 86 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-10297.php
@@ -0,0 +1,86 @@
<?php declare(strict_types = 1);

namespace Bug10297;

use Generator;
use TypeError;
use UnexpectedValueException;

/**
* @template K
* @template T
* @template L
* @template U
*
* @param iterable<K, T> $stream
* @param callable(T, K): iterable<L, U> $fn
*
* @return Generator<L, U>
*/
function scollect(iterable $stream, callable $fn): Generator
{
foreach ($stream as $key => $value) {
yield from $fn($value, $key);
}
}

/**
* @template K of array-key
* @template T
* @template L of array-key
* @template U
*
* @param array<K, T> $array
* @param callable(T, K): iterable<L, U> $fn
*
* @return array<L, U>
*/
function collectWithKeys(array $array, callable $fn): array
{
$map = [];
$counter = 0;

try {
foreach (scollect($array, $fn) as $key => $value) {
$map[$key] = $value;
++$counter;
}
} catch (TypeError) {
throw new UnexpectedValueException('The key yielded in the callable is not compatible with the type "array-key".');
}

if ($counter !== count($map)) {
throw new UnexpectedValueException(
'Data loss occurred because of duplicated keys. Use `collect()` if you do not care about ' .
'the yielded keys, or use `scollect()` if you need to support duplicated keys (as arrays cannot).',
);
}

return $map;
}

class SomeUnitTest
{
/**
* @return iterable<mixed>
*/
public static function someProvider(): iterable
{
$unsupportedTypes = [
// this one does not work:
'Not a Number' => NAN,
// these work:
'Infinity' => INF,
stdClass::class => new stdClass(),
self::class => self::class,
'hello there' => 'hello there',
'array' => [[42]],
];

yield from collectWithKeys($unsupportedTypes, static function (mixed $value, string $type): iterable {
$error = sprintf('Some %s error message', $type);

yield sprintf('"%s" something something', $type) => [$value, [$error, $error, $error]];
});
}
}
Expand Up @@ -73,4 +73,9 @@ public function testDefaultValueForPromotedProperty(): void
]);
}

public function testBug10956(): void
{
$this->analyse([__DIR__ . '/data/bug-10956.php'], []);
}

}
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-10956.php
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace Bug10956;

class HelloWorld
{
const DEFAULT_VALUE = [NAN]; // unique value, distinct from anything, but equal to itself

/**
* @param int|self::DEFAULT_VALUE $value
*/
public function test (mixed $value = self::DEFAULT_VALUE) {
if ($value === self::DEFAULT_VALUE) {
echo 'default';
} else {
echo (int) $value;
}
}
}

0 comments on commit 4d162fa

Please sign in to comment.