Skip to content

Commit 14e158e

Browse files
committed
Generics
1 parent 7e126f6 commit 14e158e

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

src/Type/Constant/ConstantArrayType.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,6 +2085,43 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
20852085
$typeMap = $typeMap->union($valueType->inferTemplateTypes($receivedValueType));
20862086
}
20872087

2088+
$unsealed = $this->getUnsealedTypes();
2089+
if ($unsealed !== null) {
2090+
[$unsealedKeyType, $unsealedValueType] = $unsealed;
2091+
2092+
// Received's explicit keys not in $this's explicit keys are
2093+
// candidates for matching $this's unsealed extras pattern.
2094+
// Only contribute when the key type matches; mismatched explicit
2095+
// keys are extra entries the parameter wouldn't accept anyway,
2096+
// surfaced by the regular argument-type check.
2097+
$receivedKeyTypes = $receivedType->getKeyTypes();
2098+
$receivedValueTypes = $receivedType->getValueTypes();
2099+
foreach ($receivedKeyTypes as $j => $receivedKeyType) {
2100+
if ($this->hasOffsetValueType($receivedKeyType)->yes()) {
2101+
continue;
2102+
}
2103+
if (!$unsealedKeyType->isSuperTypeOf($receivedKeyType)->yes()) {
2104+
continue;
2105+
}
2106+
$typeMap = $typeMap->union($unsealedKeyType->inferTemplateTypes($receivedKeyType));
2107+
$typeMap = $typeMap->union($unsealedValueType->inferTemplateTypes($receivedValueTypes[$j]));
2108+
}
2109+
2110+
// Received's own unsealed extras describe "all the rest" — when
2111+
// the key type doesn't fit $this's unsealed key pattern there
2112+
// is no valid template assignment, so force NEVER.
2113+
$receivedUnsealed = $receivedType->getUnsealedTypes();
2114+
if ($receivedUnsealed !== null) {
2115+
[$receivedUnsealedKey, $receivedUnsealedValue] = $receivedUnsealed;
2116+
if ($unsealedKeyType->isSuperTypeOf($receivedUnsealedKey)->no()) {
2117+
$typeMap = $typeMap->union($unsealedValueType->inferTemplateTypes(new NeverType()));
2118+
} else {
2119+
$typeMap = $typeMap->union($unsealedKeyType->inferTemplateTypes($receivedUnsealedKey));
2120+
$typeMap = $typeMap->union($unsealedValueType->inferTemplateTypes($receivedUnsealedValue));
2121+
}
2122+
}
2123+
}
2124+
20882125
return $typeMap;
20892126
}
20902127

tests/PHPStan/Analyser/nsrt/unsealed-array-shapes.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace UnsealedArrayShapes;
44

55
use DateTimeImmutable;
6+
use stdClass;
67
use function PHPStan\Testing\assertType;
78

89
class Foo
@@ -83,3 +84,45 @@ public function edgeCases(array $a, array $b, array $c): void
8384
}
8485

8586
}
87+
88+
class Generics
89+
{
90+
91+
/**
92+
* @template T
93+
* @param T $a
94+
* @return array{a: int, ...<int, T>}
95+
*/
96+
public function replace($a): array
97+
{
98+
99+
}
100+
101+
/**
102+
* @template T
103+
* @param array{a: int, ...<int, T>} $a
104+
* @return T
105+
*/
106+
public function infer(array $a)
107+
{
108+
109+
}
110+
111+
}
112+
113+
/**
114+
* @param Generics $g
115+
* @param array{a: 1, b: 2, ...<int, stdClass>} $a
116+
* @param array{a: 1, b: 2, ...<string, stdClass>} $b
117+
* @param array<int, stdClass> $c
118+
* @param array<string, stdClass> $d
119+
* @return void
120+
*/
121+
function doFoo(Generics $g, array $a, array $b, array $c, array $d): void {
122+
assertType('array{a: int, ...<int, stdClass>}', $g->replace(new stdClass()));
123+
assertType('1|2|3', $g->infer([1, 2, 3, 'a' => 4]));
124+
assertType('stdClass', $g->infer($a));
125+
assertType('*NEVER*', $g->infer($b));
126+
assertType('stdClass', $g->infer($c));
127+
assertType('stdClass', $g->infer($d));
128+
};

0 commit comments

Comments
 (0)