From 1bae940fcc61f7a4134ce6f08d86efbf755ff78e Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 10 Jul 2024 14:03:53 +0200 Subject: [PATCH 1/6] InvalidKeyInArray: report float deprecation since PHP 8.1 --- .../Arrays/InvalidKeyInArrayDimFetchRule.php | 25 +++++++++++++------ .../Arrays/InvalidKeyInArrayItemRule.php | 14 ++++++++++- .../InvalidKeyInArrayDimFetchRuleTest.php | 15 ++++++++--- .../Arrays/InvalidKeyInArrayItemRuleTest.php | 14 ++++++++--- .../Arrays/data/invalid-key-array-item.php | 1 + 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index 1242f85cfd..a0d129aeb0 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -22,6 +23,7 @@ class InvalidKeyInArrayDimFetchRule implements Rule public function __construct( private RuleLevelHelper $ruleLevelHelper, private bool $reportMaybes, + private PhpVersion $phpVersion, ) { } @@ -54,15 +56,24 @@ public function processNode(Node $node, Scope $scope): array } $isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); - if ($isSuperType->yes() || ($isSuperType->maybe() && !$this->reportMaybes)) { - return []; + $isOk = $isSuperType->yes() || ($isSuperType->maybe() && !$this->reportMaybes); + if (!$isOk) { + return [ + RuleErrorBuilder::message( + sprintf('%s array key type %s.', $isSuperType->no() ? 'Invalid' : 'Possibly invalid', $dimensionType->describe(VerbosityLevel::typeOnly())), + )->identifier('offsetAccess.invalidOffset')->build(), + ]; + } + + if ($this->phpVersion->getVersionId() >= 80_100 && !$dimensionType->isFloat()->no()) { + return [ + RuleErrorBuilder::message( + 'Using float as array key emits deprecation notice.', + )->identifier('array.invalidKey')->build(), + ]; } - return [ - RuleErrorBuilder::message( - sprintf('%s array key type %s.', $isSuperType->no() ? 'Invalid' : 'Possibly invalid', $dimensionType->describe(VerbosityLevel::typeOnly())), - )->identifier('offsetAccess.invalidOffset')->build(), - ]; + return []; } } diff --git a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php index 61c4fd342c..1fabd86b83 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\MixedType; @@ -16,7 +17,10 @@ class InvalidKeyInArrayItemRule implements Rule { - public function __construct(private bool $reportMaybes) + public function __construct( + private bool $reportMaybes, + private PhpVersion $phpVersion, + ) { } @@ -47,6 +51,14 @@ public function processNode(Node $node, Scope $scope): array ]; } + if ($this->phpVersion->getVersionId() >= 80_100 && !$dimensionType->isFloat()->no()) { + return [ + RuleErrorBuilder::message( + 'Using float as array key emits deprecation notice.', + )->identifier('array.invalidKey')->build(), + ]; + } + return []; } diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php index 70c671135a..bf78f23174 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php @@ -2,9 +2,12 @@ namespace PHPStan\Rules\Arrays; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use function array_filter; +use function array_values; use const PHP_VERSION_ID; /** @@ -16,12 +19,12 @@ class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase protected function getRule(): Rule { $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false); - return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true); + return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true, new PhpVersion(PHP_VERSION_ID)); } public function testInvalidKey(): void { - $this->analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], [ + $this->analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], array_values(array_filter([ [ 'Invalid array key type DateTimeImmutable.', 7, @@ -30,6 +33,12 @@ public function testInvalidKey(): void 'Invalid array key type array.', 8, ], + PHP_VERSION_ID >= 80_100 + ? [ + 'Using float as array key emits deprecation notice.', + 10, + ] + : null, [ 'Possibly invalid array key type stdClass|string.', 24, @@ -58,7 +67,7 @@ public function testInvalidKey(): void 'Invalid array key type DateTimeImmutable.', 48, ], - ]); + ]))); } public function testBug6315(): void diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php index 7a40122d1c..3ffdc97726 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php @@ -2,8 +2,10 @@ namespace PHPStan\Rules\Arrays; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function array_filter; use const PHP_VERSION_ID; /** @@ -14,12 +16,12 @@ class InvalidKeyInArrayItemRuleTest extends RuleTestCase protected function getRule(): Rule { - return new InvalidKeyInArrayItemRule(true); + return new InvalidKeyInArrayItemRule(true, new PhpVersion(PHP_VERSION_ID)); } public function testInvalidKey(): void { - $this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [ + $this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], array_filter([ [ 'Invalid array key type DateTimeImmutable.', 13, @@ -32,7 +34,13 @@ public function testInvalidKey(): void 'Possibly invalid array key type stdClass|string.', 15, ], - ]); + PHP_VERSION_ID >= 80_100 + ? [ + 'Using float as array key emits deprecation notice.', + 16, + ] + : null, + ])); } public function testInvalidKeyInList(): void diff --git a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php index cde86133df..459aa0771d 100644 --- a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php +++ b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php @@ -13,4 +13,5 @@ new \DateTimeImmutable() => 'aaa', [] => 'bbb', $stringOrObject => 'aaa', + 1.0 => 'aaa', ]; From c1e2e03e8e5f53e9777fec4d109b2b33d5a38776 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 10 Jul 2024 14:05:15 +0200 Subject: [PATCH 2/6] Fix identifier --- src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index a0d129aeb0..faf8c0128f 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -69,7 +69,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message( 'Using float as array key emits deprecation notice.', - )->identifier('array.invalidKey')->build(), + )->identifier('array.invalidOffset')->build(), ]; } From 1bef9931319567e3c2aae80c7f46da7dddda6e07 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 10 Jul 2024 14:10:08 +0200 Subject: [PATCH 3/6] Fix old php --- src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php | 2 +- src/Rules/Arrays/InvalidKeyInArrayItemRule.php | 2 +- .../PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php | 2 +- tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index faf8c0128f..74cce7e851 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -65,7 +65,7 @@ public function processNode(Node $node, Scope $scope): array ]; } - if ($this->phpVersion->getVersionId() >= 80_100 && !$dimensionType->isFloat()->no()) { + if ($this->phpVersion->getVersionId() >= 80100 && !$dimensionType->isFloat()->no()) { return [ RuleErrorBuilder::message( 'Using float as array key emits deprecation notice.', diff --git a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php index 1fabd86b83..702f13190d 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php @@ -51,7 +51,7 @@ public function processNode(Node $node, Scope $scope): array ]; } - if ($this->phpVersion->getVersionId() >= 80_100 && !$dimensionType->isFloat()->no()) { + if ($this->phpVersion->getVersionId() >= 80100 && !$dimensionType->isFloat()->no()) { return [ RuleErrorBuilder::message( 'Using float as array key emits deprecation notice.', diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php index bf78f23174..805740e196 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php @@ -33,7 +33,7 @@ public function testInvalidKey(): void 'Invalid array key type array.', 8, ], - PHP_VERSION_ID >= 80_100 + PHP_VERSION_ID >= 80100 ? [ 'Using float as array key emits deprecation notice.', 10, diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php index 3ffdc97726..8173e0fdb1 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php @@ -34,7 +34,7 @@ public function testInvalidKey(): void 'Possibly invalid array key type stdClass|string.', 15, ], - PHP_VERSION_ID >= 80_100 + PHP_VERSION_ID >= 80100 ? [ 'Using float as array key emits deprecation notice.', 16, From 2394d7f6d98c16d8448d11186d359831916f39d0 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 10 Jul 2024 14:13:36 +0200 Subject: [PATCH 4/6] Fix DuplicateKeysInLiteralArraysRule --- src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index a1eaf7e131..7d54212564 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -85,23 +85,24 @@ public function processNode(Node $node, Scope $scope): array } $value = $keyType->getValue(); + $index = (string) $value; $printedValue = $key !== null ? $this->exprPrinter->printExpr($key) : $value; - $printedValues[$value][] = $printedValue; + $printedValues[$index][] = $printedValue; - if (!isset($valueLines[$value])) { - $valueLines[$value] = $item->getStartLine(); + if (!isset($valueLines[$index])) { + $valueLines[$index] = $item->getStartLine(); } $previousCount = count($values); - $values[$value] = $printedValue; + $values[$index] = $printedValue; if ($previousCount !== count($values)) { continue; } - $duplicateKeys[$value] = true; + $duplicateKeys[$index] = true; } $messages = []; From b7f686248d78929536b2395163b0ae5411dfc133 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 10 Jul 2024 15:18:39 +0200 Subject: [PATCH 5/6] Fix DuplicateKeysInLiteralArraysRuleTest --- src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 7d54212564..89e2d65bb7 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -13,6 +13,8 @@ use function array_keys; use function count; use function implode; +use function is_float; +use function key; use function max; use function sprintf; use function var_export; @@ -85,7 +87,7 @@ public function processNode(Node $node, Scope $scope): array } $value = $keyType->getValue(); - $index = (string) $value; + $index = is_float($value) ? (int) $value : key([$value => null]); $printedValue = $key !== null ? $this->exprPrinter->printExpr($key) : $value; From 6105d2360c952b47c04e30d07287ef8f5ba82abb Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 10 Jul 2024 15:49:48 +0200 Subject: [PATCH 6/6] Fix mixed and maybe --- .../Arrays/InvalidKeyInArrayDimFetchRule.php | 22 ++++++++++++++----- .../Arrays/InvalidKeyInArrayItemRule.php | 22 ++++++++++++++----- .../InvalidKeyInArrayDimFetchRuleTest.php | 8 ++++++- .../Arrays/InvalidKeyInArrayItemRuleTest.php | 13 ++++++++--- .../data/invalid-key-array-dim-fetch.php | 5 +++++ .../Arrays/data/invalid-key-array-item.php | 8 +++++++ 6 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index 74cce7e851..38bf4f47bb 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -65,12 +65,22 @@ public function processNode(Node $node, Scope $scope): array ]; } - if ($this->phpVersion->getVersionId() >= 80100 && !$dimensionType->isFloat()->no()) { - return [ - RuleErrorBuilder::message( - 'Using float as array key emits deprecation notice.', - )->identifier('array.invalidOffset')->build(), - ]; + if ($this->phpVersion->getVersionId() >= 80100) { + $isFloat = $dimensionType->isFloat(); + + if ($isFloat->yes()) { + return [ + RuleErrorBuilder::message( + 'Float used as array key, this emits deprecation notice.', + )->identifier('array.invalidOffset')->build(), + ]; + } elseif ($this->reportMaybes && $isFloat->maybe()) { + return [ + RuleErrorBuilder::message( + 'Float possibly used as array key, this emits deprecation notice.', + )->identifier('array.invalidOffset')->build(), + ]; + } } return []; diff --git a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php index 702f13190d..43f52cee22 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php @@ -51,12 +51,22 @@ public function processNode(Node $node, Scope $scope): array ]; } - if ($this->phpVersion->getVersionId() >= 80100 && !$dimensionType->isFloat()->no()) { - return [ - RuleErrorBuilder::message( - 'Using float as array key emits deprecation notice.', - )->identifier('array.invalidKey')->build(), - ]; + if ($this->phpVersion->getVersionId() >= 80100) { + $isFloat = $dimensionType->isFloat(); + + if ($isFloat->yes()) { + return [ + RuleErrorBuilder::message( + 'Float used as array key, this emits deprecation notice.', + )->identifier('array.invalidKey')->build(), + ]; + } elseif ($this->reportMaybes && $isFloat->maybe() && !$dimensionType instanceof MixedType) { + return [ + RuleErrorBuilder::message( + 'Float possibly used as array key, this emits deprecation notice.', + )->identifier('array.invalidKey')->build(), + ]; + } } return []; diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php index 805740e196..3cfba0f143 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php @@ -35,7 +35,7 @@ public function testInvalidKey(): void ], PHP_VERSION_ID >= 80100 ? [ - 'Using float as array key emits deprecation notice.', + 'Float used as array key, this emits deprecation notice.', 10, ] : null, @@ -67,6 +67,12 @@ public function testInvalidKey(): void 'Invalid array key type DateTimeImmutable.', 48, ], + PHP_VERSION_ID >= 80100 + ? [ + 'Float possibly used as array key, this emits deprecation notice.', + 52, + ] + : null, ]))); } diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php index 8173e0fdb1..a38c2d587d 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php @@ -6,6 +6,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use function array_filter; +use function array_values; use const PHP_VERSION_ID; /** @@ -21,7 +22,7 @@ protected function getRule(): Rule public function testInvalidKey(): void { - $this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], array_filter([ + $this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], array_values(array_filter([ [ 'Invalid array key type DateTimeImmutable.', 13, @@ -36,11 +37,17 @@ public function testInvalidKey(): void ], PHP_VERSION_ID >= 80100 ? [ - 'Using float as array key emits deprecation notice.', + 'Float used as array key, this emits deprecation notice.', 16, ] : null, - ])); + PHP_VERSION_ID >= 80100 + ? [ + 'Float possibly used as array key, this emits deprecation notice.', + 24, + ] + : null, + ]))); } public function testInvalidKeyInList(): void diff --git a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php index fb7514c6cb..d4b480d1e3 100644 --- a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php +++ b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-dim-fetch.php @@ -46,3 +46,8 @@ $array[5][new \DateTimeImmutable()]; $array[new \stdClass()][new \DateTimeImmutable()]; $array[new \DateTimeImmutable()][] = 5; + +/** @var float|null $floatOrNull */ +$floatOrNull = null; +$a[$floatOrNull]; +$a[$mixed]; diff --git a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php index 459aa0771d..0f97f7c052 100644 --- a/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php +++ b/tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php @@ -14,4 +14,12 @@ [] => 'bbb', $stringOrObject => 'aaa', 1.0 => 'aaa', + $mixed => 'aaa', +]; + +/** @var float|null $floatOrNull */ +$floatOrNull = null; + +$b = [ + $floatOrNull => 'aaa', ];