Skip to content

Commit

Permalink
Scope generalization for integer range types
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 13, 2022
1 parent d4557a6 commit 3651338
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 4 deletions.
80 changes: 80 additions & 0 deletions src/Analyser/MutatingScope.php
Expand Up @@ -133,6 +133,8 @@
use function strtolower;
use function substr;
use function usort;
use const PHP_INT_MAX;
use const PHP_INT_MIN;

class MutatingScope implements Scope
{
Expand Down Expand Up @@ -4679,6 +4681,7 @@ private static function generalizeType(Type $a, Type $b): Type
$constantStrings = ['a' => [], 'b' => []];
$constantArrays = ['a' => [], 'b' => []];
$generalArrays = ['a' => [], 'b' => []];
$integerRanges = ['a' => [], 'b' => []];
$otherTypes = [];

foreach ([
Expand Down Expand Up @@ -4710,6 +4713,10 @@ private static function generalizeType(Type $a, Type $b): Type
$generalArrays[$key][] = $type;
continue;
}
if ($type instanceof IntegerRangeType) {
$integerRanges[$key][] = $type;
continue;
}

$otherTypes[] = $type;
}
Expand Down Expand Up @@ -4857,6 +4864,79 @@ private static function generalizeType(Type $a, Type $b): Type
$resultTypes[] = TypeCombinator::union(...$constantIntegers['b']);
}

if (count($integerRanges['a']) > 0) {
if (count($integerRanges['b']) === 0) {
$resultTypes[] = TypeCombinator::union(...$integerRanges['a']);
} else {
$integerRangesA = TypeCombinator::union(...$integerRanges['a']);
$integerRangesB = TypeCombinator::union(...$integerRanges['b']);

if ($integerRangesA->equals($integerRangesB)) {
$resultTypes[] = $integerRangesA;
} else {
$min = null;
$max = null;
foreach ($integerRanges['a'] as $range) {
if ($range->getMin() === null) {
$rangeMin = PHP_INT_MIN;
} else {
$rangeMin = $range->getMin();
}
if ($range->getMax() === null) {
$rangeMax = PHP_INT_MAX;
} else {
$rangeMax = $range->getMax();
}

if ($min === null || $rangeMin < $min) {
$min = $rangeMin;
}
if ($max !== null && $rangeMax <= $max) {
continue;
}

$max = $rangeMax;
}

$gotGreater = false;
$gotSmaller = false;
foreach ($integerRanges['b'] as $range) {
if ($range->getMin() === null) {
$rangeMin = PHP_INT_MIN;
} else {
$rangeMin = $range->getMin();
}
if ($range->getMax() === null) {
$rangeMax = PHP_INT_MAX;
} else {
$rangeMax = $range->getMax();
}

if ($rangeMax > $max) {
$gotGreater = true;
}
if ($rangeMin >= $min) {
continue;
}

$gotSmaller = true;
}

if ($gotGreater && $gotSmaller) {
$resultTypes[] = new IntegerType();
} elseif ($gotGreater) {
$resultTypes[] = IntegerRangeType::fromInterval($min, null);
} elseif ($gotSmaller) {
$resultTypes[] = IntegerRangeType::fromInterval(null, $max);
} else {
$resultTypes[] = TypeCombinator::union($integerRangesA, $integerRangesB);
}
}
}
} elseif (count($integerRanges['b']) > 0) {
$resultTypes[] = TypeCombinator::union(...$integerRanges['b']);
}

return TypeCombinator::union(...$resultTypes, ...$otherTypes);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Expand Up @@ -7097,7 +7097,7 @@ public function dataForLoopVariables(): array
"'end'",
],
[
'int<0, 10>',
'int<0, max>',
'$i',
"'afterLoop'",
],
Expand Down
67 changes: 67 additions & 0 deletions tests/PHPStan/Analyser/ScopeTest.php
Expand Up @@ -9,6 +9,7 @@
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
Expand Down Expand Up @@ -155,6 +156,72 @@ public function dataGeneralize(): array
]),
'array<literal-string&non-empty-string, int<1, max>>',
],
[
new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
]),
new UnionType([
new ConstantIntegerType(-1),
new ConstantIntegerType(0),
new ConstantIntegerType(1),
]),
'int<min, 1>',
],
[
new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(2),
]),
new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
new ConstantIntegerType(2),
]),
'0|1|2',
],
[
new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
new ConstantIntegerType(2),
]),
new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(2),
]),
'0|1|2',
],
[
IntegerRangeType::fromInterval(0, 16),
IntegerRangeType::fromInterval(1, 17),
'int<0, max>',
],
[
IntegerRangeType::fromInterval(0, 16),
IntegerRangeType::fromInterval(-1, 15),
'int<min, 16>',
],
[
IntegerRangeType::fromInterval(0, 16),
IntegerRangeType::fromInterval(1, null),
'int<0, max>',
],
[
IntegerRangeType::fromInterval(0, 16),
IntegerRangeType::fromInterval(null, 15),
'int<min, 16>',
],
[
IntegerRangeType::fromInterval(0, 16),
IntegerRangeType::fromInterval(0, null),
'int<0, max>',
],
[
IntegerRangeType::fromInterval(0, 16),
IntegerRangeType::fromInterval(null, 16),
'int<min, 16>',
],
];
}

Expand Down
6 changes: 3 additions & 3 deletions tests/PHPStan/Analyser/data/for-loop-i-type.php
Expand Up @@ -14,14 +14,14 @@ public function doBar() {
assertType('int<1, 49>', $i);
}

assertType('50', $i);
assertType('int<50, max>', $i);
assertType(\stdClass::class, $foo);

for($i = 50; $i > 0; $i--) {
assertType('int<1, 50>', $i);
}

assertType('0', $i);
assertType('int<min, 0>', $i);
}

public function doCount(array $a) {
Expand Down Expand Up @@ -59,7 +59,7 @@ public function doLOrem() {
break;
}

assertType('int<1, 50>', $i);
assertType('int<1, max>', $i);
}

}
Expand Up @@ -73,4 +73,14 @@ public function testBug3867(): void
$this->analyse([__DIR__ . '/data/bug-3867.php'], []);
}

public function testIntegerRangeGeneralization(): void
{
$this->analyse([__DIR__ . '/data/integer-range-generalization.php'], []);
}

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

}
49 changes: 49 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-3153.php
@@ -0,0 +1,49 @@
<?php

namespace Bug3153;

class Foo
{

public function doFoo()
{
$x = random_int(1, 1000);

if($x < 7){
while(true){
$x++;

if( $x > 10 ){
break;
}
}
}
}

public function doBar()
{
$rows = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
$added_rows = 0;
$limit = random_int(1, 20);

foreach($rows as $row){

if( $added_rows >= $limit ){
break;
}
$added_rows++;
}

if( $added_rows < 3 ){
foreach($rows as $row){

$added_rows++;

if( $added_rows > 10 ){
break;
}
}
}
}

}
@@ -0,0 +1,28 @@
<?php

namespace IntegerRangeGenerealization;

class Foo
{

public function doFoo(string $array): string
{
$result = str_repeat("\x00", 4096);
if($array !== $result){
$i = 0;
for($x = 0; $x < 16; ++$x){
$zM = $x + 256;
for($z = $x; $z < $zM; $z += 16){
$yM = $z + 4096;
for($y = $z; $y < $yM; $y += 256){
$result[$i] = $array[$y];
++$i;
}
}
}
}

return $result;
}

}

0 comments on commit 3651338

Please sign in to comment.