Skip to content

Commit

Permalink
ConstantArrayTypeBuilder - preserve ConstantArrayType for integer ran…
Browse files Browse the repository at this point in the history
…ge offsets
  • Loading branch information
ondrejmirtes committed Jan 12, 2022
1 parent ebfe7cf commit 3eab462
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 48 deletions.
111 changes: 64 additions & 47 deletions src/Type/Constant/ConstantArrayTypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use function count;
use function is_float;
use function max;
use function range;

/** @api */
class ConstantArrayTypeBuilder
Expand Down Expand Up @@ -59,67 +60,83 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt
$offsetType = ArrayType::castToArrayKeyType($offsetType);
}

if (
!$this->degradeToGeneralArray
&& ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType)
) {
/** @var ConstantIntegerType|ConstantStringType $keyType */
foreach ($this->keyTypes as $i => $keyType) {
if ($keyType->getValue() === $offsetType->getValue()) {
$this->valueTypes[$i] = $valueType;
$this->optionalKeys = array_values(array_filter($this->optionalKeys, static fn (int $index): bool => $index !== $i));
return;
if (!$this->degradeToGeneralArray) {
if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) {
/** @var ConstantIntegerType|ConstantStringType $keyType */
foreach ($this->keyTypes as $i => $keyType) {
if ($keyType->getValue() === $offsetType->getValue()) {
$this->valueTypes[$i] = $valueType;
$this->optionalKeys = array_values(array_filter($this->optionalKeys, static fn (int $index): bool => $index !== $i));
return;
}
}
}

$this->keyTypes[] = $offsetType;
$this->valueTypes[] = $valueType;
$this->keyTypes[] = $offsetType;
$this->valueTypes[] = $valueType;

if ($optional) {
$this->optionalKeys[] = count($this->keyTypes) - 1;
}
if ($optional) {
$this->optionalKeys[] = count($this->keyTypes) - 1;
}

/** @var int|float $newNextAutoIndex */
$newNextAutoIndex = $offsetType instanceof ConstantIntegerType
? max($this->nextAutoIndex, $offsetType->getValue() + 1)
: $this->nextAutoIndex;
if (!is_float($newNextAutoIndex)) {
$this->nextAutoIndex = $newNextAutoIndex;
/** @var int|float $newNextAutoIndex */
$newNextAutoIndex = $offsetType instanceof ConstantIntegerType
? max($this->nextAutoIndex, $offsetType->getValue() + 1)
: $this->nextAutoIndex;
if (!is_float($newNextAutoIndex)) {
$this->nextAutoIndex = $newNextAutoIndex;
}
return;
}
return;
}

$scalarTypes = TypeUtils::getConstantScalars($offsetType);
if (!$this->degradeToGeneralArray && count($scalarTypes) > 0) {
$match = true;
$valueTypes = $this->valueTypes;
foreach ($scalarTypes as $scalarType) {
$scalarOffsetType = ArrayType::castToArrayKeyType($scalarType);
if (!$scalarOffsetType instanceof ConstantIntegerType && !$scalarOffsetType instanceof ConstantStringType) {
throw new ShouldNotHappenException();
$scalarTypes = TypeUtils::getConstantScalars($offsetType);
if (count($scalarTypes) === 0) {
$integerRanges = TypeUtils::getIntegerRanges($offsetType);
if (count($integerRanges) > 0) {
foreach ($integerRanges as $integerRange) {
if ($integerRange->getMin() === null) {
break;
}
if ($integerRange->getMax() === null) {
break;
}

foreach (range($integerRange->getMin(), $integerRange->getMax()) as $rangeValue) {
$scalarTypes[] = new ConstantIntegerType($rangeValue);
}
}
}
$offsetMatch = false;
}
if (count($scalarTypes) > 0 && count($scalarTypes) < self::ARRAY_COUNT_LIMIT) {
$match = true;
$valueTypes = $this->valueTypes;
foreach ($scalarTypes as $scalarType) {
$scalarOffsetType = ArrayType::castToArrayKeyType($scalarType);
if (!$scalarOffsetType instanceof ConstantIntegerType && !$scalarOffsetType instanceof ConstantStringType) {
throw new ShouldNotHappenException();
}
$offsetMatch = false;

/** @var ConstantIntegerType|ConstantStringType $keyType */
foreach ($this->keyTypes as $i => $keyType) {
if ($keyType->getValue() !== $scalarOffsetType->getValue()) {
/** @var ConstantIntegerType|ConstantStringType $keyType */
foreach ($this->keyTypes as $i => $keyType) {
if ($keyType->getValue() !== $scalarOffsetType->getValue()) {
continue;
}

$valueTypes[$i] = TypeCombinator::union($valueTypes[$i], $valueType);
$offsetMatch = true;
}

if ($offsetMatch) {
continue;
}

$valueTypes[$i] = TypeCombinator::union($valueTypes[$i], $valueType);
$offsetMatch = true;
$match = false;
}

if ($offsetMatch) {
continue;
if ($match) {
$this->valueTypes = $valueTypes;
return;
}

$match = false;
}

if ($match) {
$this->valueTypes = $valueTypes;
return;
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/Type/TypeUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ public static function getDirectClassNames(Type $type): array
return [];
}

/**
* @return IntegerRangeType[]
*/
public static function getIntegerRanges(Type $type): array
{
return self::map(IntegerRangeType::class, $type, false);
}

/**
* @return ConstantScalarType[]
*/
Expand Down
57 changes: 56 additions & 1 deletion tests/PHPStan/Analyser/data/constant-array-type-set.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function doFoo(int $i)
/** @var int<0, 2> $offset2 */
$offset2 = doFoo();
$d[$offset2] = true;
//assertType('array{bool, bool, bool}', $d);
assertType('array{bool, bool, bool}', $d);

$e = [false, false, false];
/** @var 0|1|2|3 $offset3 */
Expand All @@ -42,4 +42,59 @@ public function doFoo(int $i)
assertType('array{bool, bool, false}', $f);
}

/**
* @param int<0, 1> $offset
* @return void
*/
public function doBar(int $offset): void
{
$a = [false, false, false];
$a[$offset] = true;
assertType('array{bool, bool, false}', $a);
}

/**
* @param int<0, 1>|int<3, 4> $offset
* @return void
*/
public function doBar2(int $offset): void
{
$a = [false, false, false, false, false];
$a[$offset] = true;
assertType('array{bool, bool, false, bool, bool}', $a);
}

/**
* @param int<0, max> $offset
* @return void
*/
public function doBar3(int $offset): void
{
$a = [false, false, false, false, false];
$a[$offset] = true;
assertType('non-empty-array<int<0, max>, bool>', $a);
}

/**
* @param int<min, 0> $offset
* @return void
*/
public function doBar4(int $offset): void
{
$a = [false, false, false, false, false];
$a[$offset] = true;
assertType('non-empty-array<int<min, 4>, bool>', $a);
}

/**
* @param int<0, 4> $offset
* @return void
*/
public function doBar5(int $offset): void
{
$a = [false, false, false];
$a[$offset] = true;
assertType('non-empty-array<int<0, 4>, bool>', $a);
}

}

0 comments on commit 3eab462

Please sign in to comment.