Skip to content

Commit 4d02dba

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents 91c6623 + 5a70c2d commit 4d02dba

File tree

5 files changed

+447
-303
lines changed

5 files changed

+447
-303
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,23 @@ public function __construct(
122122
$this->isList = $isList;
123123
}
124124

125+
/**
126+
* @param list<ConstantIntegerType|ConstantStringType> $keyTypes
127+
* @param array<int, Type> $valueTypes
128+
* @param non-empty-list<int> $nextAutoIndexes
129+
* @param int[] $optionalKeys
130+
*/
131+
protected function recreate(
132+
array $keyTypes,
133+
array $valueTypes,
134+
array $nextAutoIndexes = [0],
135+
array $optionalKeys = [],
136+
?TrinaryLogic $isList = null,
137+
): self
138+
{
139+
return new self($keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $isList);
140+
}
141+
125142
public function getConstantArrays(): array
126143
{
127144
return [$this];
@@ -765,7 +782,7 @@ public function unsetOffset(Type $offsetType, bool $preserveListCertainty = fals
765782
return new NeverType();
766783
}
767784

768-
return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, $newIsList);
785+
return $this->recreate($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, $newIsList);
769786
}
770787

771788
return $this;
@@ -812,7 +829,7 @@ public function unsetOffset(Type $offsetType, bool $preserveListCertainty = fals
812829
return new NeverType();
813830
}
814831

815-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
832+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
816833
}
817834

818835
$optionalKeys = $this->optionalKeys;
@@ -842,7 +859,7 @@ public function unsetOffset(Type $offsetType, bool $preserveListCertainty = fals
842859
return new NeverType();
843860
}
844861

845-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
862+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
846863
}
847864

848865
/**
@@ -1066,7 +1083,7 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
10661083

10671084
if ($length === 0 || ($offset < 0 && $length < 0 && $offset - $length >= 0)) {
10681085
// 0 / 0, 3 / 0 or e.g. -3 / -3 or -3 / -4 and so on never extract anything
1069-
return new self([], []);
1086+
return $this->recreate([], []);
10701087
}
10711088

10721089
if ($length < 0) {
@@ -1370,7 +1387,7 @@ private function removeLastElements(int $length): self
13701387
$optionalKeysRemoved--;
13711388
}
13721389

1373-
return new self(
1390+
return $this->recreate(
13741391
$keyTypes,
13751392
$valueTypes,
13761393
$nextAutoindexes,
@@ -1474,7 +1491,7 @@ public function generalizeValues(): self
14741491
$valueTypes[] = $valueType->generalize(GeneralizePrecision::lessSpecific());
14751492
}
14761493

1477-
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
1494+
return $this->recreate($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
14781495
}
14791496

14801497
private function degradeToGeneralArray(): Type
@@ -1522,7 +1539,7 @@ private function getKeysOrValuesArray(array $types): self
15221539
static fn (int $i): ConstantIntegerType => new ConstantIntegerType($i),
15231540
array_keys($types),
15241541
);
1525-
return new self($keyTypes, $types, $autoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
1542+
return $this->recreate($keyTypes, $types, $autoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
15261543
}
15271544

15281545
$keyTypes = [];
@@ -1551,7 +1568,7 @@ private function getKeysOrValuesArray(array $types): self
15511568
$maxIndex++;
15521569
}
15531570

1554-
return new self($keyTypes, $valueTypes, $autoIndexes, $optionalKeys, TrinaryLogic::createYes());
1571+
return $this->recreate($keyTypes, $valueTypes, $autoIndexes, $optionalKeys, TrinaryLogic::createYes());
15551572
}
15561573

15571574
public function describe(VerbosityLevel $level): string
@@ -1716,7 +1733,7 @@ public function traverse(callable $cb): Type
17161733
return $this;
17171734
}
17181735

1719-
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
1736+
return $this->recreate($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
17201737
}
17211738

17221739
public function traverseSimultaneously(Type $right, callable $cb): Type
@@ -1742,7 +1759,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type
17421759
return $this;
17431760
}
17441761

1745-
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
1762+
return $this->recreate($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
17461763
}
17471764

17481765
public function isKeysSupersetOf(self $otherArray): bool
@@ -1820,7 +1837,7 @@ public function mergeWith(self $otherArray): self
18201837
$nextAutoIndexes = array_values(array_unique(array_merge($this->nextAutoIndexes, $otherArray->nextAutoIndexes)));
18211838
sort($nextAutoIndexes);
18221839

1823-
return new self($this->keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $this->isList->and($otherArray->isList));
1840+
return $this->recreate($this->keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $this->isList->and($otherArray->isList));
18241841
}
18251842

18261843
/**
@@ -1874,7 +1891,7 @@ public function makeOffsetRequired(Type $offsetType): self
18741891
}
18751892

18761893
if (count($this->optionalKeys) !== count($optionalKeys)) {
1877-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, array_values($optionalKeys), $this->isList);
1894+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, array_values($optionalKeys), $this->isList);
18781895
}
18791896

18801897
break;
@@ -1893,7 +1910,7 @@ public function makeList(): Type
18931910
return new NeverType();
18941911
}
18951912

1896-
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
1913+
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $this->optionalKeys, TrinaryLogic::createYes());
18971914
}
18981915

18991916
public function toPhpDocNode(): TypeNode

src/Type/Generic/TemplateConstantArrayType.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace PHPStan\Type\Generic;
44

5+
use PHPStan\TrinaryLogic;
56
use PHPStan\Type\Constant\ConstantArrayType;
7+
use PHPStan\Type\Constant\ConstantIntegerType;
8+
use PHPStan\Type\Constant\ConstantStringType;
69
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
710
use PHPStan\Type\Type;
811

@@ -35,4 +38,28 @@ public function __construct(
3538
$this->default = $default;
3639
}
3740

41+
/**
42+
* @param list<ConstantIntegerType|ConstantStringType> $keyTypes
43+
* @param array<int, Type> $valueTypes
44+
* @param non-empty-list<int> $nextAutoIndexes
45+
* @param int[] $optionalKeys
46+
*/
47+
protected function recreate(
48+
array $keyTypes,
49+
array $valueTypes,
50+
array $nextAutoIndexes = [0],
51+
array $optionalKeys = [],
52+
?TrinaryLogic $isList = null,
53+
): ConstantArrayType
54+
{
55+
return new self(
56+
$this->scope,
57+
$this->strategy,
58+
$this->variance,
59+
$this->name,
60+
new ConstantArrayType($keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $isList),
61+
$this->default,
62+
);
63+
}
64+
3865
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Bug12355;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @phpstan-type AnimalData array{
9+
* animalSpecificData: ValidType,
10+
* habitat?: string,
11+
* }
12+
* @template ValidType of array{
13+
* name: string,
14+
* ...,
15+
* }
16+
*/
17+
abstract class Animal
18+
{
19+
/**
20+
* @param AnimalData $arg
21+
*/
22+
public function __construct(array $arg) {
23+
assertType('ValidType of array{name: string} (class Bug12355\Animal, argument)', $arg['animalSpecificData']);
24+
if (isset($arg['habitat'])) {
25+
//do things
26+
}
27+
assertType('ValidType of array{name: string} (class Bug12355\Animal, argument)', $arg['animalSpecificData']);
28+
}
29+
}
30+
31+
/**
32+
* @template T of array{name: string, ...}
33+
* @param array{first: T, second: T, flag?: bool} $arg
34+
*/
35+
function testMergeWithDifferentObjects(array $arg): void
36+
{
37+
assertType('T of array{name: string} (function Bug12355\testMergeWithDifferentObjects(), argument)', $arg['first']);
38+
39+
// Modifying $arg in one branch causes different ConstantArrayType objects
40+
if (isset($arg['flag'])) {
41+
$arg['extra'] = true;
42+
}
43+
44+
// After scope merge, $arg's value types for 'first' and 'second' go through
45+
// ConstantArrayType::mergeWith() which uses new self() — stripping TemplateConstantArrayType
46+
assertType('T of array{name: string} (function Bug12355\testMergeWithDifferentObjects(), argument)', $arg['first']);
47+
assertType('T of array{name: string} (function Bug12355\testMergeWithDifferentObjects(), argument)', $arg['second']);
48+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Bug9828;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T of array{value: int, ...}
9+
* @param array<int, T> $array
10+
* @return array<int, T>
11+
*/
12+
function filter(array $array, int $min_value): array
13+
{
14+
foreach ($array as $key => $row) {
15+
if ($row['value'] < $min_value) unset($array[$key]);
16+
}
17+
return array_values($array);
18+
}
19+
20+
/**
21+
* @template T of array{value: int, ...}
22+
* @param array<int, T> $array
23+
* @return list<T>
24+
*/
25+
function filter2(array $array, int $min_value): array
26+
{
27+
$result = [];
28+
foreach ($array as $row) {
29+
if ($row['value'] >= $min_value) $result[] = $row;
30+
}
31+
return $result;
32+
}
33+
34+
/**
35+
* @param array<int, array{value: int, name: string}> $data
36+
*/
37+
function test(array $data): void
38+
{
39+
assertType('array<int, array{value: int, name: string}>', filter($data, 5));
40+
assertType('list<array{value: int, name: string}>', filter2($data, 5));
41+
}

0 commit comments

Comments
 (0)