Skip to content

Commit 920f872

Browse files
authored
More precise mixed-type subtraction in toInteger()
1 parent c7b3443 commit 920f872

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

src/Type/MixedType.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use PHPStan\Type\Accessory\OversizedArrayType;
2828
use PHPStan\Type\Constant\ConstantArrayType;
2929
use PHPStan\Type\Constant\ConstantBooleanType;
30+
use PHPStan\Type\Constant\ConstantIntegerType;
3031
use PHPStan\Type\Generic\TemplateMixedType;
3132
use PHPStan\Type\Generic\TemplateType;
3233
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
@@ -484,10 +485,10 @@ public function toBoolean(): BooleanType
484485

485486
public function toNumber(): Type
486487
{
487-
return new UnionType([
488+
return TypeCombinator::union(
488489
$this->toInteger(),
489490
$this->toFloat(),
490-
]);
491+
);
491492
}
492493

493494
public function toAbsoluteNumber(): Type
@@ -497,6 +498,24 @@ public function toAbsoluteNumber(): Type
497498

498499
public function toInteger(): Type
499500
{
501+
$castsToZero = new UnionType([
502+
new NullType(),
503+
new ConstantBooleanType(false),
504+
new ConstantIntegerType(0),
505+
new ConstantArrayType([], []),
506+
new StringType(),
507+
new FloatType(),
508+
]);
509+
if (
510+
$this->subtractedType !== null
511+
&& $this->subtractedType->isSuperTypeOf($castsToZero)->yes()
512+
) {
513+
return new UnionType([
514+
IntegerRangeType::fromInterval(null, -1),
515+
IntegerRangeType::fromInterval(1, null),
516+
]);
517+
}
518+
500519
return new IntegerType();
501520
}
502521

tests/PHPStan/Analyser/nsrt/integer-range-types.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,44 @@ public function zeroIssues($positive, $negative)
446446
}
447447

448448
}
449+
450+
function subtract($m) {
451+
if ($m != 0) {
452+
assertType("mixed", $m); // could be "mixed~0|0.0|''|'0'|array{}|false|null"
453+
assertType('int', (int) $m);
454+
}
455+
if ($m !== 0) {
456+
assertType("mixed~0", $m);
457+
assertType('int', (int) $m); // mixed could still contain falsey values, which cast to 0
458+
}
459+
if (!is_int($m)) {
460+
assertType("mixed~int", $m);
461+
assertType('int', (int) $m); // mixed could still contain falsey values, which cast to 0
462+
}
463+
464+
if ($m != true) {
465+
assertType("0|0.0|''|'0'|array{}|false|null", $m);
466+
assertType('0', (int) $m);
467+
}
468+
if ($m !== true) {
469+
assertType("mixed~true", $m);
470+
assertType('int', (int) $m);
471+
}
472+
473+
if ($m != false) {
474+
assertType("mixed~0|0.0|''|'0'|array{}|false|null", $m);
475+
assertType('int', (int) $m);
476+
}
477+
if ($m !== false) {
478+
assertType("mixed~false", $m);
479+
assertType('int', (int) $m); // mixed could still contain falsey values, which cast to 0
480+
}
481+
if (!is_string($m) && !is_float($m)) {
482+
assertType("mixed~float|string", $m);
483+
assertType('int', (int) $m);
484+
if ($m != false) {
485+
assertType("mixed~0|array{}|float|string|false|null", $m);
486+
assertType('int<min, -1>|int<1, max>', (int) $m);
487+
}
488+
}
489+
}

0 commit comments

Comments
 (0)