Skip to content

Commit adac30e

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Preserve constant array shape when setting union of constant scalar keys
- Modified ConstantArrayTypeBuilder::setOffsetValueType() to add unmatched scalar keys as optional entries instead of degrading to a general array, when none of the union members match existing keys and the array is non-empty - New regression test in tests/PHPStan/Analyser/nsrt/bug-12665.php - New test for non-loop union offset setting in nsrt/set-constant-union-offset-on-constant-array.php - Updated array-fill-keys test expectations to reflect improved type precision Closes phpstan/phpstan#12665
1 parent 050e6bf commit adac30e

4 files changed

Lines changed: 63 additions & 2 deletions

File tree

src/Type/Constant/ConstantArrayTypeBuilder.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt
258258
}
259259
if (count($scalarTypes) > 0 && count($scalarTypes) < self::ARRAY_COUNT_LIMIT) {
260260
$match = true;
261+
$hasMatch = false;
261262
$valueTypes = $this->valueTypes;
262263
foreach ($scalarTypes as $scalarType) {
263264
$offsetMatch = false;
@@ -273,6 +274,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt
273274
}
274275

275276
if ($offsetMatch) {
277+
$hasMatch = true;
276278
continue;
277279
}
278280

@@ -283,6 +285,26 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt
283285
$this->valueTypes = $valueTypes;
284286
return;
285287
}
288+
289+
if (!$hasMatch && count($this->keyTypes) > 0) {
290+
foreach ($scalarTypes as $scalarType) {
291+
$this->keyTypes[] = $scalarType;
292+
$this->valueTypes[] = $valueType;
293+
$this->optionalKeys[] = count($this->keyTypes) - 1;
294+
}
295+
296+
$this->isList = TrinaryLogic::createNo();
297+
298+
if (
299+
!$this->disableArrayDegradation
300+
&& count($this->keyTypes) > self::ARRAY_COUNT_LIMIT
301+
) {
302+
$this->degradeToGeneralArray = true;
303+
$this->oversized = true;
304+
}
305+
306+
return;
307+
}
286308
}
287309

288310
$this->isList = TrinaryLogic::createNo();

tests/PHPStan/Analyser/nsrt/array-fill-keys.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ function withObjectKey() : array
5454
function withUnionKeys(): void
5555
{
5656
$arr1 = ['foo', rand(0, 1) ? 'bar1' : 'bar2', 'baz'];
57-
assertType("non-empty-array<'bar1'|'bar2'|'baz'|'foo', 'b'>", array_fill_keys($arr1, 'b'));
57+
assertType("array{foo: 'b', bar1?: 'b', bar2?: 'b', baz: 'b'}", array_fill_keys($arr1, 'b'));
5858

5959
$arr2 = ['foo'];
6060
if (rand(0, 1)) {
6161
$arr2[] = 'bar';
6262
}
6363
$arr2[] = 'baz';
64-
assertType("non-empty-array<'bar'|'baz'|'foo', 'b'>", array_fill_keys($arr2, 'b'));
64+
assertType("array{foo: 'b', bar?: 'b', baz?: 'b'}", array_fill_keys($arr2, 'b'));
6565
}
6666

6767
function withOptionalKeys(): void
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12665;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Broken
8+
{
9+
/** @return array{a: string, b: int, c: int} */
10+
public function break(string $s, int $i): array
11+
{
12+
$array = ['a' => $s];
13+
foreach (['b', 'c'] as $letter) {
14+
$array[$letter] = $i;
15+
}
16+
assertType('array{a: string, b?: int, c?: int}', $array);
17+
return $array;
18+
}
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace SetConstantUnionOffsetOnConstantArray;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param array{foo: int} $a
12+
*/
13+
public function doFoo(array $a): void
14+
{
15+
$k = rand(0, 1) ? 'a' : 'b';
16+
$a[$k] = 256;
17+
assertType('array{foo: int, a?: 256, b?: 256}', $a);
18+
}
19+
20+
}

0 commit comments

Comments
 (0)