Skip to content

Commit 1fa16cd

Browse files
Infer more duplicated array keys
1 parent e099481 commit 1fa16cd

File tree

3 files changed

+91
-8
lines changed

3 files changed

+91
-8
lines changed

src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
use PHPStan\Node\Printer\ExprPrinter;
99
use PHPStan\Rules\Rule;
1010
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Type\Constant\ConstantIntegerType;
1112
use PHPStan\Type\ConstantScalarType;
1213
use function array_keys;
1314
use function count;
1415
use function implode;
16+
use function max;
1517
use function sprintf;
1618
use function var_export;
1719

@@ -38,25 +40,55 @@ public function processNode(Node $node, Scope $scope): array
3840
$duplicateKeys = [];
3941
$printedValues = [];
4042
$valueLines = [];
43+
44+
/**
45+
* @var int|false|null $autoGeneratedIndex
46+
* - An int value represent the biggest integer used as array key.
47+
* When no key is provided this value + 1 will be used.
48+
* - Null is used as initializer instead of 0 to avoid issue with negative keys.
49+
* - False means a non-scalar value was encountered and we cannot be sure of the next keys.
50+
*/
51+
$autoGeneratedIndex = null;
4152
foreach ($node->getItemNodes() as $itemNode) {
4253
$item = $itemNode->getArrayItem();
4354
if ($item === null) {
44-
continue;
45-
}
46-
if ($item->key === null) {
55+
$autoGeneratedIndex = false;
4756
continue;
4857
}
4958

5059
$key = $item->key;
51-
$keyType = $itemNode->getScope()->getType($key);
52-
if (
53-
!$keyType instanceof ConstantScalarType
54-
) {
60+
if ($key === null) {
61+
if ($autoGeneratedIndex === false) {
62+
continue;
63+
}
64+
65+
if ($autoGeneratedIndex === null) {
66+
$autoGeneratedIndex = 0;
67+
$keyType = new ConstantIntegerType(0);
68+
} else {
69+
$keyType = new ConstantIntegerType(++$autoGeneratedIndex);
70+
}
71+
} else {
72+
$keyType = $itemNode->getScope()->getType($key);
73+
74+
$arrayKeyValue = $keyType->toArrayKey();
75+
if ($arrayKeyValue instanceof ConstantIntegerType) {
76+
$autoGeneratedIndex = $autoGeneratedIndex === null
77+
? $arrayKeyValue->getValue()
78+
: max($autoGeneratedIndex, $arrayKeyValue->getValue());
79+
}
80+
}
81+
82+
if (!$keyType instanceof ConstantScalarType) {
83+
$autoGeneratedIndex = false;
5584
continue;
5685
}
5786

58-
$printedValue = $this->exprPrinter->printExpr($key);
5987
$value = $keyType->getValue();
88+
$printedValue = $key !== null
89+
? $this->exprPrinter->printExpr($key)
90+
: $value;
91+
6092
$printedValues[$value][] = $printedValue;
6193

6294
if (!isset($valueLines[$value])) {

tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ public function testDuplicateKeys(): void
4545
'Array has 2 duplicate keys with value 2 ($idx, $idx).',
4646
55,
4747
],
48+
[
49+
'Array has 2 duplicate keys with value 0 (0, 0).',
50+
63,
51+
],
52+
[
53+
'Array has 2 duplicate keys with value 101 (101, 101).',
54+
67,
55+
],
56+
[
57+
'Array has 2 duplicate keys with value 102 (102, 102).',
58+
69,
59+
],
60+
[
61+
'Array has 2 duplicate keys with value -41 (-41, -41).',
62+
76,
63+
],
4864
]);
4965
}
5066

tests/PHPStan/Rules/Arrays/data/duplicate-keys.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,39 @@ public function doIncrement2()
5757
];
5858
}
5959

60+
public function doWithoutKeys(int $int)
61+
{
62+
$foo = [
63+
1, // Key is 0
64+
0 => 2,
65+
100 => 3,
66+
'This key is ignored' => 42,
67+
4, // Key is 101
68+
10 => 5,
69+
6, // Key is 102
70+
101 => 7,
71+
102 => 8,
72+
];
73+
74+
$foo2 = [
75+
'-42' => 1,
76+
2, // The key is -41
77+
0 => 3,
78+
-41 => 4,
79+
];
80+
81+
$foo3 = [
82+
$int => 33,
83+
0 => 1,
84+
2, // Because of `$int` key, the key value cannot be known.
85+
1 => 3,
86+
];
87+
88+
$foo4 = [
89+
1,
90+
2,
91+
3,
92+
];
93+
}
94+
6095
}

0 commit comments

Comments
 (0)