From adac30e7f26217277b26183ddda213dd98b29675 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:58:18 +0000 Subject: [PATCH] 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 https://github.com/phpstan/phpstan/issues/12665 --- .../Constant/ConstantArrayTypeBuilder.php | 22 +++++++++++++++++++ .../PHPStan/Analyser/nsrt/array-fill-keys.php | 4 ++-- tests/PHPStan/Analyser/nsrt/bug-12665.php | 19 ++++++++++++++++ ...onstant-union-offset-on-constant-array.php | 20 +++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12665.php create mode 100644 tests/PHPStan/Analyser/nsrt/set-constant-union-offset-on-constant-array.php diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index ad9340252e..c901306f28 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -258,6 +258,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt } if (count($scalarTypes) > 0 && count($scalarTypes) < self::ARRAY_COUNT_LIMIT) { $match = true; + $hasMatch = false; $valueTypes = $this->valueTypes; foreach ($scalarTypes as $scalarType) { $offsetMatch = false; @@ -273,6 +274,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt } if ($offsetMatch) { + $hasMatch = true; continue; } @@ -283,6 +285,26 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->valueTypes = $valueTypes; return; } + + if (!$hasMatch && count($this->keyTypes) > 0) { + foreach ($scalarTypes as $scalarType) { + $this->keyTypes[] = $scalarType; + $this->valueTypes[] = $valueType; + $this->optionalKeys[] = count($this->keyTypes) - 1; + } + + $this->isList = TrinaryLogic::createNo(); + + if ( + !$this->disableArrayDegradation + && count($this->keyTypes) > self::ARRAY_COUNT_LIMIT + ) { + $this->degradeToGeneralArray = true; + $this->oversized = true; + } + + return; + } } $this->isList = TrinaryLogic::createNo(); diff --git a/tests/PHPStan/Analyser/nsrt/array-fill-keys.php b/tests/PHPStan/Analyser/nsrt/array-fill-keys.php index 9a56ef0cfb..39d1df4b5d 100644 --- a/tests/PHPStan/Analyser/nsrt/array-fill-keys.php +++ b/tests/PHPStan/Analyser/nsrt/array-fill-keys.php @@ -54,14 +54,14 @@ function withObjectKey() : array function withUnionKeys(): void { $arr1 = ['foo', rand(0, 1) ? 'bar1' : 'bar2', 'baz']; - assertType("non-empty-array<'bar1'|'bar2'|'baz'|'foo', 'b'>", array_fill_keys($arr1, 'b')); + assertType("array{foo: 'b', bar1?: 'b', bar2?: 'b', baz: 'b'}", array_fill_keys($arr1, 'b')); $arr2 = ['foo']; if (rand(0, 1)) { $arr2[] = 'bar'; } $arr2[] = 'baz'; - assertType("non-empty-array<'bar'|'baz'|'foo', 'b'>", array_fill_keys($arr2, 'b')); + assertType("array{foo: 'b', bar?: 'b', baz?: 'b'}", array_fill_keys($arr2, 'b')); } function withOptionalKeys(): void diff --git a/tests/PHPStan/Analyser/nsrt/bug-12665.php b/tests/PHPStan/Analyser/nsrt/bug-12665.php new file mode 100644 index 0000000000..d488e450ea --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12665.php @@ -0,0 +1,19 @@ + $s]; + foreach (['b', 'c'] as $letter) { + $array[$letter] = $i; + } + assertType('array{a: string, b?: int, c?: int}', $array); + return $array; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/set-constant-union-offset-on-constant-array.php b/tests/PHPStan/Analyser/nsrt/set-constant-union-offset-on-constant-array.php new file mode 100644 index 0000000000..15638a7bd4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/set-constant-union-offset-on-constant-array.php @@ -0,0 +1,20 @@ +