Skip to content

Commit

Permalink
TypeCombinator: convert adjacent ints to ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
GKFX committed Jan 11, 2021
1 parent 1b8734f commit 7b0412a
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 1 deletion.
30 changes: 29 additions & 1 deletion src/Type/TypeCombinator.php
Expand Up @@ -171,7 +171,8 @@ public static function union(Type ...$types): Type
}
if ($types[$i] instanceof ConstantScalarType) {
$type = $types[$i];
$scalarTypes[get_class($type)][md5($type->describe(VerbosityLevel::cache()))] = $type;
$key = $type instanceof ConstantIntegerType ? $type->getValue() : md5($type->describe(VerbosityLevel::cache()));
$scalarTypes[get_class($type)][$key] = $type;
unset($types[$i]);
continue;
}
Expand Down Expand Up @@ -256,6 +257,33 @@ public static function union(Type ...$types): Type
$types[] = new BooleanType();
continue;
}
if ($classType === ConstantIntegerType::class) {
/** @var int[] */
$integers = array_keys($scalarTypeItems);
sort($integers);
/** @var ConstantIntegerType[] */
$loneIntegers = [];
$rangeStart = null;
$rangeEnd = null;
for ($i = 0; $i < count($integers); $i++) {
if ($i < count($integers) - 1 && $integers[$i] + 1 === $integers[$i + 1]) {
$rangeStart ??= $integers[$i];
$rangeEnd = $integers[$i + 1];
} elseif ($rangeStart === null) {
$loneIntegers[] = $scalarTypeItems[$integers[$i]];
} else {
$types[] = IntegerRangeType::fromInterval($rangeStart, $rangeEnd);
$rangeStart = null;
$rangeEnd = null;
}
}
if (count($loneIntegers) > self::CONSTANT_SCALAR_UNION_THRESHOLD) {
$types[] = new IntegerType();
} else {
array_push($types, ...$loneIntegers);
}
continue;
}
foreach ($scalarTypeItems as $type) {
if (count($scalarTypeItems) > self::CONSTANT_SCALAR_UNION_THRESHOLD) {
$types[] = $type->generalize();
Expand Down
4 changes: 4 additions & 0 deletions tests/PHPStan/Rules/Classes/InstantiationRuleTest.php
Expand Up @@ -193,6 +193,10 @@ public function testInstantiation(): void
'Class TestInstantiation\ClassExtendingAbstractConstructor constructor invoked with 0 parameters, 1 required.',
273,
],
[
'Parameter #2 $y of class TestInstantiation\IntRange constructor expects int<1,7>, int<1, 8> given.',
291,
],
]
);
}
Expand Down
26 changes: 26 additions & 0 deletions tests/PHPStan/Rules/Classes/data/instantiation.php
Expand Up @@ -274,3 +274,29 @@ public function doBar()
}

}

final class IntRange
{
/**
* @psalm-var 1|2|3|4|5|6|7|8
*/
private int $x;

public static function fromInt(int $x): self
{
if ($x < 1 || $x > 8) {
throw new InvalidArgumentException;
}

return new self($x, $x);
}

/**
* @psalm-param 1|2|3|4|5|6|7|8 $x
* @psalm-param 1|2|3|4|5|6|7 $y
*/
private function __construct(int $x, int $y)
{
$y = $this->x = $x;
}
}
4 changes: 4 additions & 0 deletions tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php
Expand Up @@ -61,6 +61,10 @@ public function testReturnTypeRule(): void
'Function ReturnTypes\returnNever() should never return but return statement found.',
181,
],
[
'Function ReturnTypes\returnRangeBad() should return int<1, 7> but returns int<1, 8>.',
203,
],
]);
}

Expand Down
22 changes: 22 additions & 0 deletions tests/PHPStan/Rules/Functions/data/returnTypes.php
Expand Up @@ -180,3 +180,25 @@ function returnNever()
{
return;
}

/**
* @return 1|2|3|4|5|6|7|8
*/
function returnRange(int $x) : int {
if ($x < 1 || $x > 8) {
throw new InvalidArgumentException;
}

return $x;
}

/**
* @return 1|2|3|4|5|6|7
*/
function returnRangeBad(int $x) : int {
if ($x < 1 || $x > 8) {
throw new InvalidArgumentException;
}

return $x;
}
27 changes: 27 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Expand Up @@ -1760,6 +1760,33 @@ public function dataUnion(): array
MixedType::class,
'mixed=implicit',
],
[
[
new ConstantIntegerType(1),
new ConstantIntegerType(2),
new ConstantIntegerType(3),
new ConstantIntegerType(4),
new ConstantIntegerType(5),
new ConstantIntegerType(6),
],
IntegerRangeType::class,
'int<1,6>',
],
[
[
new ConstantIntegerType(1),
new ConstantIntegerType(2),
new ConstantIntegerType(3),
new ConstantIntegerType(5),
new ConstantIntegerType(7),
new ConstantIntegerType(8),
new ConstantIntegerType(9),
new ConstantIntegerType(10),
new ConstantIntegerType(11),
],
UnionType::class,
'int<1,5>|7|int<8,11>',
],
];
}

Expand Down

0 comments on commit 7b0412a

Please sign in to comment.