Skip to content

Commit ec117fa

Browse files
committed
Improve preserving ConstantArrayType after setting new offset
1 parent 7b03aee commit ec117fa

File tree

4 files changed

+77
-1
lines changed

4 files changed

+77
-1
lines changed

Diff for: src/Type/Constant/ConstantArrayTypeBuilder.php

+36
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace PHPStan\Type\Constant;
44

5+
use PHPStan\ShouldNotHappenException;
56
use PHPStan\Type\Accessory\NonEmptyArrayType;
67
use PHPStan\Type\ArrayType;
78
use PHPStan\Type\Type;
89
use PHPStan\Type\TypeCombinator;
10+
use PHPStan\Type\TypeUtils;
911
use function array_filter;
1012
use function array_values;
1113
use function count;
@@ -87,6 +89,40 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt
8789
return;
8890
}
8991

92+
$scalarTypes = TypeUtils::getConstantScalars($offsetType);
93+
if (!$this->degradeToGeneralArray && count($scalarTypes) > 0) {
94+
$match = true;
95+
$valueTypes = $this->valueTypes;
96+
foreach ($scalarTypes as $scalarType) {
97+
$scalarOffsetType = ArrayType::castToArrayKeyType($scalarType);
98+
if (!$scalarOffsetType instanceof ConstantIntegerType && !$scalarOffsetType instanceof ConstantStringType) {
99+
throw new ShouldNotHappenException();
100+
}
101+
$offsetMatch = false;
102+
103+
/** @var ConstantIntegerType|ConstantStringType $keyType */
104+
foreach ($this->keyTypes as $i => $keyType) {
105+
if ($keyType->getValue() !== $scalarOffsetType->getValue()) {
106+
continue;
107+
}
108+
109+
$valueTypes[$i] = TypeCombinator::union($valueTypes[$i], $valueType);
110+
$offsetMatch = true;
111+
}
112+
113+
if ($offsetMatch) {
114+
continue;
115+
}
116+
117+
$match = false;
118+
}
119+
120+
if ($match) {
121+
$this->valueTypes = $valueTypes;
122+
return;
123+
}
124+
}
125+
90126
$this->keyTypes[] = $offsetType;
91127
$this->valueTypes[] = $valueType;
92128
if ($optional) {

Diff for: tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -8107,7 +8107,7 @@ public function dataArrayKeysInBranches(): array
81078107
'$arrayAppendedInForeach',
81088108
],
81098109
[
8110-
'array<int, \'bar\'|\'baz\'|\'foo\'>',
8110+
'array<int, literal-string&non-empty-string>', // could be 'array<int, \'bar\'|\'baz\'|\'foo\'>'
81118111
'$anotherArrayAppendedInForeach',
81128112
],
81138113
[

Diff for: tests/PHPStan/Analyser/NodeScopeResolverTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ public function dataFileAsserts(): iterable
595595

596596
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-destructuring-types.php');
597597
yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-prepare.php');
598+
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-type-set.php');
598599
}
599600

600601
/**
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace ConstantArrayTypeSet;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doFoo(int $i)
11+
{
12+
$a = [1, 2, 3];
13+
$a[$i] = 4;
14+
assertType('non-empty-array<int, 1|2|3|4>', $a);
15+
16+
$b = [1, 2, 3];
17+
$b[3] = 4;
18+
assertType('array{1, 2, 3, 4}', $b);
19+
20+
$c = [false, false, false];
21+
/** @var 0|1|2 $offset */
22+
$offset = doFoo();
23+
$c[$offset] = true;
24+
assertType('array{bool, bool, bool}', $c);
25+
26+
$d = [false, false, false];
27+
/** @var int<0, 2> $offset2 */
28+
$offset2 = doFoo();
29+
$d[$offset2] = true;
30+
//assertType('array{bool, bool, bool}', $d);
31+
32+
$e = [false, false, false];
33+
/** @var 0|1|2|3 $offset3 */
34+
$offset3 = doFoo();
35+
$e[$offset3] = true;
36+
assertType('non-empty-array<0|1|2|3, bool>', $e);
37+
}
38+
39+
}

0 commit comments

Comments
 (0)