Skip to content

Commit 7fe1a6a

Browse files
Split 0.0 and -0.0
1 parent 745ae5a commit 7fe1a6a

23 files changed

+147
-84
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19911991
new ConstantBooleanType(false),
19921992
new ConstantIntegerType(0),
19931993
new ConstantFloatType(0.0),
1994+
new ConstantFloatType(-0.0),
19941995
new ConstantStringType(''),
19951996
new ConstantArrayType([], []),
19961997
];
@@ -2023,6 +2024,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
20232024
new ConstantBooleanType(false),
20242025
new ConstantIntegerType(0),
20252026
new ConstantFloatType(0.0),
2027+
new ConstantFloatType(-0.0),
20262028
new StringType(),
20272029
];
20282030
} else {
@@ -2031,6 +2033,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
20312033
new ConstantBooleanType(false),
20322034
new ConstantIntegerType(0),
20332035
new ConstantFloatType(0.0),
2036+
new ConstantFloatType(-0.0),
20342037
new ConstantStringType('0'),
20352038
];
20362039
}
@@ -2047,6 +2050,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
20472050
new ConstantBooleanType(false),
20482051
new ConstantIntegerType(0),
20492052
new ConstantFloatType(0.0),
2053+
new ConstantFloatType(-0.0),
20502054
new ConstantStringType(''),
20512055
];
20522056
} else {
@@ -2474,16 +2478,21 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
24742478
&& !$rightType->equals($leftType)
24752479
&& $rightType->isSuperTypeOf($leftType)->yes())
24762480
) {
2481+
$typeToSet = $leftType;
2482+
if (\in_array(0.0, $leftType->getConstantScalarValues(), true)) {
2483+
$typeToSet = TypeCombinator::union($typeToSet, new ConstantFloatType(0.0), new ConstantFloatType(-0.0));
2484+
}
2485+
24772486
$types = $this->create(
24782487
$rightExpr,
2479-
$leftType,
2488+
$typeToSet,
24802489
$context,
24812490
$scope,
24822491
)->setRootExpr($expr);
24832492
if ($rightExpr instanceof AlwaysRememberedExpr) {
24842493
$types = $types->unionWith($this->create(
24852494
$unwrappedRightExpr,
2486-
$leftType,
2495+
$typeToSet,
24872496
$context,
24882497
$scope,
24892498
))->setRootExpr($expr);
@@ -2498,16 +2507,21 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
24982507
&& $leftType->isSuperTypeOf($rightType)->yes()
24992508
)
25002509
) {
2510+
$typeToSet = $rightType;
2511+
if (\in_array(0.0, $rightType->getConstantScalarValues(), true)) {
2512+
$typeToSet = TypeCombinator::union($typeToSet, new ConstantFloatType(0.0), new ConstantFloatType(-0.0));
2513+
}
2514+
25012515
$leftTypes = $this->create(
25022516
$leftExpr,
2503-
$rightType,
2517+
$typeToSet,
25042518
$context,
25052519
$scope,
25062520
)->setRootExpr($expr);
25072521
if ($leftExpr instanceof AlwaysRememberedExpr) {
25082522
$leftTypes = $leftTypes->unionWith($this->create(
25092523
$unwrappedLeftExpr,
2510-
$rightType,
2524+
$typeToSet,
25112525
$context,
25122526
$scope,
25132527
))->setRootExpr($expr);

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,14 +1236,14 @@ public function getMulType(Expr $left, Expr $right, callable $getTypeCallback):
12361236
$leftNumberType = $leftType->toNumber();
12371237
if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) {
12381238
if ($rightType->isFloat()->yes()) {
1239-
return new ConstantFloatType(0.0);
1239+
return new UnionType([new ConstantFloatType(0.0), new ConstantFloatType(-0.0)]);
12401240
}
12411241
return new ConstantIntegerType(0);
12421242
}
12431243
$rightNumberType = $rightType->toNumber();
12441244
if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) {
12451245
if ($leftType->isFloat()->yes()) {
1246-
return new ConstantFloatType(0.0);
1246+
return new UnionType([new ConstantFloatType(0.0), new ConstantFloatType(-0.0)]);
12471247
}
12481248
return new ConstantIntegerType(0);
12491249
}

src/Type/Constant/ConstantFloatType.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,19 @@ public function getValue(): float
4040

4141
public function equals(Type $type): bool
4242
{
43-
return $type instanceof self && ($this->value === $type->value || is_nan($this->value) && is_nan($type->value));
43+
if (!$type instanceof self) {
44+
return false;
45+
}
46+
47+
if (is_nan($this->value)) {
48+
return is_nan($type->value);
49+
}
50+
51+
if ($this->value === 0.0 && $type->value === 0.0) {
52+
return (string) $this->value === (string) $type->value;
53+
}
54+
55+
return $this->value === $type->value;
4456
}
4557

4658
private function castFloatToString(float $value): string

src/Type/NullType.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ public function getSmallerOrEqualType(PhpVersion $phpVersion): Type
366366
new ConstantBooleanType(false),
367367
new ConstantIntegerType(0),
368368
new ConstantFloatType(0.0),
369+
new ConstantFloatType(-0.0),
369370
new ConstantStringType(''),
370371
new ConstantArrayType([], []),
371372
]);
@@ -379,6 +380,7 @@ public function getGreaterType(PhpVersion $phpVersion): Type
379380
new ConstantBooleanType(false),
380381
new ConstantIntegerType(0),
381382
new ConstantFloatType(0.0),
383+
new ConstantFloatType(-0.0),
382384
new ConstantStringType(''),
383385
new ConstantArrayType([], []),
384386
]));

src/Type/StaticTypeFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static function falsey(): Type
2121
new ConstantBooleanType(false),
2222
new ConstantIntegerType(0),
2323
new ConstantFloatType(0.0),
24+
new ConstantFloatType(-0.0),
2425
new ConstantStringType(''),
2526
new ConstantStringType('0'),
2627
new ConstantArrayType([], []),

src/Type/Traits/ConstantNumericComparisonTypeTrait.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function getSmallerType(PhpVersion $phpVersion): Type
2525
$subtractedTypes[] = new NullType();
2626
$subtractedTypes[] = new ConstantBooleanType(false);
2727
$subtractedTypes[] = new ConstantFloatType(0.0); // subtract range when we support float-ranges
28+
$subtractedTypes[] = new ConstantFloatType(-0.0); // subtract range when we support float-ranges
2829
}
2930

3031
return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
@@ -50,6 +51,7 @@ public function getGreaterType(PhpVersion $phpVersion): Type
5051
new NullType(),
5152
new ConstantBooleanType(false),
5253
new ConstantFloatType(0.0), // subtract range when we support float-ranges
54+
new ConstantFloatType(-0.0), // subtract range when we support float-ranges
5355
IntegerRangeType::createAllSmallerThanOrEqualTo($this->value),
5456
];
5557

@@ -70,6 +72,7 @@ public function getGreaterOrEqualType(PhpVersion $phpVersion): Type
7072
$subtractedTypes[] = new NullType();
7173
$subtractedTypes[] = new ConstantBooleanType(false);
7274
$subtractedTypes[] = new ConstantFloatType(0.0); // subtract range when we support float-ranges
75+
$subtractedTypes[] = new ConstantFloatType(-0.0); // subtract range when we support float-ranges
7376
}
7477

7578
return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));

tests/PHPStan/Analyser/TypeSpecifierTest.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
class TypeSpecifierTest extends PHPStanTestCase
4747
{
4848

49-
private const FALSEY_TYPE_DESCRIPTION = '0|0.0|\'\'|\'0\'|array{}|false|null';
49+
private const FALSEY_TYPE_DESCRIPTION = '0|0.0|-0.0|\'\'|\'0\'|array{}|false|null';
5050
private const TRUTHY_TYPE_DESCRIPTION = 'mixed~(' . self::FALSEY_TYPE_DESCRIPTION . ')';
5151
private const SURE_NOT_FALSEY = '~' . self::FALSEY_TYPE_DESCRIPTION;
5252
private const SURE_NOT_TRUTHY = '~' . self::TRUTHY_TYPE_DESCRIPTION;
@@ -481,8 +481,8 @@ public static function dataCondition(): iterable
481481
new Variable('foo'),
482482
new Expr\ConstFetch(new Name('null')),
483483
),
484-
['$foo' => '0|0.0|\'\'|array{}|false|null'],
485-
['$foo' => '~0|0.0|\'\'|array{}|false|null'],
484+
['$foo' => '0|0.0|-0.0|\'\'|array{}|false|null'],
485+
['$foo' => '~0|0.0|-0.0|\'\'|array{}|false|null'],
486486
],
487487
[
488488
new Expr\BinaryOp\Identical(
@@ -633,7 +633,7 @@ public static function dataCondition(): iterable
633633
[
634634
new Expr\BooleanNot(new Expr\Empty_(new Variable('stringOrNull'))),
635635
[
636-
'$stringOrNull' => '~0|0.0|\'\'|\'0\'|array{}|false|null',
636+
'$stringOrNull' => '~0|0.0|-0.0|\'\'|\'0\'|array{}|false|null',
637637
],
638638
[
639639
'$stringOrNull' => '\'\'|\'0\'|null',
@@ -655,13 +655,13 @@ public static function dataCondition(): iterable
655655
'$array' => 'array{}|null',
656656
],
657657
[
658-
'$array' => '~0|0.0|\'\'|\'0\'|array{}|false|null',
658+
'$array' => '~0|0.0|-0.0|\'\'|\'0\'|array{}|false|null',
659659
],
660660
],
661661
[
662662
new BooleanNot(new Expr\Empty_(new Variable('array'))),
663663
[
664-
'$array' => '~0|0.0|\'\'|\'0\'|array{}|false|null',
664+
'$array' => '~0|0.0|-0.0|\'\'|\'0\'|array{}|false|null',
665665
],
666666
[
667667
'$array' => 'array{}|null',
@@ -826,7 +826,7 @@ public static function dataCondition(): iterable
826826
'$n' => 'mixed~(int<3, max>|true)',
827827
],
828828
[
829-
'$n' => 'mixed~(0.0|int<min, 2>|false|null)',
829+
'$n' => 'mixed~(0.0|-0.0|int<min, 2>|false|null)',
830830
],
831831
],
832832
[
@@ -838,7 +838,7 @@ public static function dataCondition(): iterable
838838
'$n' => 'mixed~(int<' . PHP_INT_MIN . ', max>|true)',
839839
],
840840
[
841-
'$n' => 'mixed~(0.0|false|null)',
841+
'$n' => 'mixed~(0.0|-0.0|false|null)',
842842
],
843843
],
844844
[
@@ -847,7 +847,7 @@ public static function dataCondition(): iterable
847847
new LNumber(PHP_INT_MAX),
848848
),
849849
[
850-
'$n' => 'mixed~(0.0|bool|int<min, ' . PHP_INT_MAX . '>|null)',
850+
'$n' => 'mixed~(0.0|-0.0|bool|int<min, ' . PHP_INT_MAX . '>|null)',
851851
],
852852
[
853853
'$n' => 'mixed',
@@ -862,7 +862,7 @@ public static function dataCondition(): iterable
862862
'$n' => 'mixed~int<' . (PHP_INT_MIN + 1) . ', max>',
863863
],
864864
[
865-
'$n' => 'mixed~(0.0|bool|int<min, ' . PHP_INT_MIN . '>|null)',
865+
'$n' => 'mixed~(0.0|-0.0|bool|int<min, ' . PHP_INT_MIN . '>|null)',
866866
],
867867
],
868868
[
@@ -871,7 +871,7 @@ public static function dataCondition(): iterable
871871
new LNumber(PHP_INT_MAX),
872872
),
873873
[
874-
'$n' => 'mixed~(0.0|int<min, ' . (PHP_INT_MAX - 1) . '>|false|null)',
874+
'$n' => 'mixed~(0.0|-0.0|int<min, ' . (PHP_INT_MAX - 1) . '>|false|null)',
875875
],
876876
[
877877
'$n' => 'mixed~(int<' . PHP_INT_MAX . ', max>|true)',
@@ -889,7 +889,7 @@ public static function dataCondition(): iterable
889889
),
890890
),
891891
[
892-
'$n' => 'mixed~(0.0|int<min, 2>|int<6, max>|false|null)',
892+
'$n' => 'mixed~(0.0|-0.0|int<min, 2>|int<6, max>|false|null)',
893893
],
894894
[
895895
'$n' => 'mixed~(int<3, 5>|true)',
@@ -1252,7 +1252,7 @@ public static function dataCondition(): iterable
12521252
),
12531253
[
12541254
'$foo' => 'non-empty-array<mixed, mixed>',
1255-
'count($foo)' => 'mixed~(0.0|int<min, 1>|false|null)',
1255+
'count($foo)' => 'mixed~(0.0|-0.0|int<min, 1>|false|null)',
12561256
],
12571257
[],
12581258
],

tests/PHPStan/Analyser/nsrt/assert-empty.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ function assertNotEmpty(mixed $var): void
2020

2121
function ($var) {
2222
assertEmpty($var);
23-
assertType("0|0.0|''|'0'|array{}|false|null", $var);
23+
assertType("0|0.0|-0.0|''|'0'|array{}|false|null", $var);
2424
};
2525

2626
function ($var) {
2727
assertNotEmpty($var);
28-
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $var);
28+
assertType("mixed~(0|0.0|-0.0|''|'0'|array{}|false|null)", $var);
2929
};

tests/PHPStan/Analyser/nsrt/bug-10189.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function file_managed_file_save_upload(): array {
2020
}
2121
assertType('non-empty-array|Bug10189\SomeInterface', $files);
2222
$files = array_filter($files);
23-
assertType("array<mixed~(0|0.0|''|'0'|array{}|false|null)>", $files);
23+
assertType("array<mixed~(0|0.0|-0.0|''|'0'|array{}|false|null)>", $files);
2424

2525
return empty($files) ? [] : [1,2];
2626
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12225;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function float_sign(float $num): ?int
8+
{
9+
if ($num === 0.0) {
10+
assertType('0.0|-0.0', $num);
11+
assertType("'-0'|'0'", (string) $num);
12+
}
13+
assertType("'0'", (string) 0.0);
14+
assertType("'-0'", (string) -0.0);
15+
16+
assertType('true', 0.0 === -0.0);
17+
18+
return null;
19+
}
20+
21+
function withMixed(mixed $num): ?int
22+
{
23+
if ($num === 0.0) {
24+
assertType('0.0|-0.0', $num);
25+
assertType("'-0'|'0'", (string) $num);
26+
}
27+
28+
assertType('true', 0.0 === -0.0);
29+
30+
return null;
31+
}

0 commit comments

Comments
 (0)