-
Notifications
You must be signed in to change notification settings - Fork 428
/
ConstantArrayTypeBuilder.php
130 lines (109 loc) · 3.35 KB
/
ConstantArrayTypeBuilder.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php declare(strict_types = 1);
namespace PHPStan\Type\Constant;
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use function array_filter;
use function array_values;
use function count;
use function is_float;
use function max;
/** @api */
class ConstantArrayTypeBuilder
{
public const ARRAY_COUNT_LIMIT = 256;
private bool $degradeToGeneralArray = false;
/**
* @param array<int, Type> $keyTypes
* @param array<int, Type> $valueTypes
* @param array<int> $optionalKeys
*/
private function __construct(
private array $keyTypes,
private array $valueTypes,
private int $nextAutoIndex,
private array $optionalKeys,
)
{
}
public static function createEmpty(): self
{
return new self([], [], 0, []);
}
public static function createFromConstantArray(ConstantArrayType $startArrayType): self
{
return new self(
$startArrayType->getKeyTypes(),
$startArrayType->getValueTypes(),
$startArrayType->getNextAutoIndex(),
$startArrayType->getOptionalKeys(),
);
}
public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $optional = false): void
{
if ($offsetType === null) {
$offsetType = new ConstantIntegerType($this->nextAutoIndex);
} else {
$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;
}
}
$this->keyTypes[] = $offsetType;
$this->valueTypes[] = $valueType;
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;
}
return;
}
$this->keyTypes[] = TypeUtils::generalizeType($offsetType, GeneralizePrecision::moreSpecific());
$this->valueTypes[] = $valueType;
if ($optional) {
$this->optionalKeys[] = count($this->keyTypes) - 1;
}
$this->degradeToGeneralArray = true;
}
public function degradeToGeneralArray(): void
{
$this->degradeToGeneralArray = true;
}
public function getArray(): Type
{
$keyTypesCount = count($this->keyTypes);
if ($keyTypesCount === 0) {
return new ConstantArrayType([], []);
}
if (!$this->degradeToGeneralArray) {
/** @var array<int, ConstantIntegerType|ConstantStringType> $keyTypes */
$keyTypes = $this->keyTypes;
return new ConstantArrayType($keyTypes, $this->valueTypes, $this->nextAutoIndex, $this->optionalKeys);
}
$array = new ArrayType(
TypeCombinator::union(...$this->keyTypes),
TypeCombinator::union(...$this->valueTypes),
);
if (count($this->optionalKeys) < $keyTypesCount) {
return TypeCombinator::intersect($array, new NonEmptyArrayType());
}
return $array;
}
}