Skip to content

Commit 1bd3083

Browse files
phpstan-botclaude
andcommitted
Use toArrayKey() for proper array key coercion and add tests for multiple scalar keys
Array keys in PHP undergo type coercion (e.g. string "2" becomes int 2), so use toArrayKey() before checking key values for implicit index tracking. Also handle multiple-scalar keys correctly: string-only keys don't affect implicit int indices. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 01977ee commit 1bd3083

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

src/Analyser/ExprHandler/AssignHandler.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -946,15 +946,18 @@ private function processArrayByRefItems(MutatingScope $scope, string $rootVarNam
946946
$implicitIndex = 0;
947947
foreach ($arrayExpr->items as $arrayItem) {
948948
if ($arrayItem->key !== null && $implicitIndex !== null) {
949-
$keyValues = $scope->getType($arrayItem->key)->getConstantScalarValues();
949+
$keyType = $scope->getType($arrayItem->key)->toArrayKey();
950+
$keyValues = $keyType->getConstantScalarValues();
950951
if (count($keyValues) === 1) {
951952
$keyValue = $keyValues[0];
952953
if (is_int($keyValue) && $keyValue >= $implicitIndex) {
953954
$implicitIndex = $keyValue + 1;
954955
}
956+
} elseif ($keyType->isInteger()->no()) {
957+
// All possible key values are strings, so they don't affect implicit int indices
955958
} else {
956-
// Non-constant key makes subsequent implicit indices unpredictable,
957-
// so we stop tracking implicit indices for the rest of the array
959+
// Key could be an integer but we don't know which one,
960+
// so subsequent implicit indices are unpredictable
958961
$implicitIndex = null;
959962
}
960963
}

tests/PHPStan/Analyser/nsrt/bug-14333.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,45 @@ function testMultipleScalarKeyValues(bool $key): void
8585
assertType('1|2|3', $a);
8686
}
8787

88+
/** @param 'a'|'b' $key */
89+
function testMultipleScalarKey(string $key): void
90+
{
91+
$a = 1;
92+
93+
$b = [$key => 'x', &$a];
94+
assertType('1', $a);
95+
96+
// $key has multiple possible values but both are strings,
97+
// so the implicit index for &$a is still 0
98+
$b[0] = 2;
99+
assertType('2', $a);
100+
}
101+
102+
/** @param 0|1 $key */
103+
function testMultipleIntScalarKey(int $key): void
104+
{
105+
$a = 1;
106+
107+
$b = [$key => 'x', &$a];
108+
assertType('1', $a);
109+
110+
// $key could be 0 or 1, so implicit index could be 1 or 2 — unpredictable
111+
$b[0] = 2;
112+
assertType('1', $a);
113+
}
114+
115+
function testStringNumericKey(): void
116+
{
117+
$a = 1;
118+
119+
// PHP coerces string "2" to int 2 as array key, so next implicit index is 3
120+
$b = ['2' => 'x', &$a];
121+
assertType('1', $a);
122+
123+
$b[3] = 2;
124+
assertType('2', $a);
125+
}
126+
88127
function foo(array &$a): void {}
89128

90129
function testFunctionCall() {

0 commit comments

Comments
 (0)