From 080b9492f655c0042667d255e8ffcce281649150 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 9 May 2024 23:01:28 +0200 Subject: [PATCH 1/2] Infer some duplicated keys --- .../DuplicateKeysInLiteralArraysRule.php | 48 ++++++++++++++++--- .../DuplicateKeysInLiteralArraysRuleTest.php | 16 +++++++ .../Rules/Arrays/data/duplicate-keys.php | 29 +++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 606ef7aa20..5f2d07893b 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -8,6 +8,7 @@ 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; @@ -38,25 +39,58 @@ public function processNode(Node $node, Scope $scope): array $duplicateKeys = []; $printedValues = []; $valueLines = []; + + /** + * @var int|null|false $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) { + $autoGeneratedIndex = false; continue; } - if ($item->key === null) { + + $key = $item->key; + if ($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) { + $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])) { diff --git a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php index e0a4ce3798..87b5a12e5f 100644 --- a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php @@ -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, + ], ]); } diff --git a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php index f0ddf61b94..99242c2883 100644 --- a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php +++ b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php @@ -57,4 +57,33 @@ 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, + ]; + } + } From b128c7069d53c0266be7d0146f0efed4188810fe Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 10 May 2024 00:01:59 +0200 Subject: [PATCH 2/2] Fix --- src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php | 8 +++----- tests/PHPStan/Rules/Arrays/data/duplicate-keys.php | 6 ++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 5f2d07893b..a1eaf7e131 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -13,6 +13,7 @@ use function array_keys; use function count; use function implode; +use function max; use function sprintf; use function var_export; @@ -41,7 +42,7 @@ public function processNode(Node $node, Scope $scope): array $valueLines = []; /** - * @var int|null|false $autoGeneratedIndex + * @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. @@ -56,16 +57,13 @@ public function processNode(Node $node, Scope $scope): array } $key = $item->key; - if ($key === null && $autoGeneratedIndex === false) { - continue; - } - if ($key === null) { if ($autoGeneratedIndex === false) { continue; } if ($autoGeneratedIndex === null) { + $autoGeneratedIndex = 0; $keyType = new ConstantIntegerType(0); } else { $keyType = new ConstantIntegerType(++$autoGeneratedIndex); diff --git a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php index 99242c2883..02176773ac 100644 --- a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php +++ b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php @@ -84,6 +84,12 @@ public function doWithoutKeys(int $int) 2, // Because of `$int` key, the key value cannot be known. 1 => 3, ]; + + $foo4 = [ + 1, + 2, + 3, + ]; } }