Skip to content

Commit

Permalink
Infer more duplicated array keys
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentLanglet authored May 30, 2024
1 parent e099481 commit 1fa16cd
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 8 deletions.
48 changes: 40 additions & 8 deletions src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
use PHPStan\Node\Printer\ExprPrinter;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\ConstantScalarType;
use function array_keys;
use function count;
use function implode;
use function max;
use function sprintf;
use function var_export;

Expand All @@ -38,25 +40,55 @@ public function processNode(Node $node, Scope $scope): array
$duplicateKeys = [];
$printedValues = [];
$valueLines = [];

/**
* @var int|false|null $autoGeneratedIndex
* - An int value represent the biggest integer used as array key.
* When no key is provided this value + 1 will be used.
* - Null is used as initializer instead of 0 to avoid issue with negative keys.
* - False means a non-scalar value was encountered and we cannot be sure of the next keys.
*/
$autoGeneratedIndex = null;
foreach ($node->getItemNodes() as $itemNode) {
$item = $itemNode->getArrayItem();
if ($item === null) {
continue;
}
if ($item->key === null) {
$autoGeneratedIndex = false;
continue;
}

$key = $item->key;
$keyType = $itemNode->getScope()->getType($key);
if (
!$keyType instanceof ConstantScalarType
) {
if ($key === null) {
if ($autoGeneratedIndex === false) {
continue;
}

if ($autoGeneratedIndex === null) {
$autoGeneratedIndex = 0;
$keyType = new ConstantIntegerType(0);
} else {
$keyType = new ConstantIntegerType(++$autoGeneratedIndex);
}
} else {
$keyType = $itemNode->getScope()->getType($key);

$arrayKeyValue = $keyType->toArrayKey();
if ($arrayKeyValue instanceof ConstantIntegerType) {
$autoGeneratedIndex = $autoGeneratedIndex === null
? $arrayKeyValue->getValue()
: max($autoGeneratedIndex, $arrayKeyValue->getValue());
}
}

if (!$keyType instanceof ConstantScalarType) {
$autoGeneratedIndex = false;
continue;
}

$printedValue = $this->exprPrinter->printExpr($key);
$value = $keyType->getValue();
$printedValue = $key !== null
? $this->exprPrinter->printExpr($key)
: $value;

$printedValues[$value][] = $printedValue;

if (!isset($valueLines[$value])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ public function testDuplicateKeys(): void
'Array has 2 duplicate keys with value 2 ($idx, $idx).',
55,
],
[
'Array has 2 duplicate keys with value 0 (0, 0).',
63,
],
[
'Array has 2 duplicate keys with value 101 (101, 101).',
67,
],
[
'Array has 2 duplicate keys with value 102 (102, 102).',
69,
],
[
'Array has 2 duplicate keys with value -41 (-41, -41).',
76,
],
]);
}

Expand Down
35 changes: 35 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/duplicate-keys.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,39 @@ public function doIncrement2()
];
}

public function doWithoutKeys(int $int)
{
$foo = [
1, // Key is 0
0 => 2,
100 => 3,
'This key is ignored' => 42,
4, // Key is 101
10 => 5,
6, // Key is 102
101 => 7,
102 => 8,
];

$foo2 = [
'-42' => 1,
2, // The key is -41
0 => 3,
-41 => 4,
];

$foo3 = [
$int => 33,
0 => 1,
2, // Because of `$int` key, the key value cannot be known.
1 => 3,
];

$foo4 = [
1,
2,
3,
];
}

}

0 comments on commit 1fa16cd

Please sign in to comment.