From 5e15e2e3479d63092e50b921382168b6d79d0343 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 27 Jul 2025 00:22:04 +0200 Subject: [PATCH 1/4] Add integration test --- .../PHPStan/Levels/LevelsIntegrationTest.php | 1 + .../Levels/data/arrayOffsetAccess-3.json | 7 ++++ .../Levels/data/arrayOffsetAccess-4.json | 37 +++++++++++++++++++ .../Levels/data/arrayOffsetAccess-6.json | 12 ++++++ .../Levels/data/arrayOffsetAccess-7.json | 12 ++++++ .../PHPStan/Levels/data/arrayOffsetAccess.php | 24 ++++++++++++ 6 files changed, 93 insertions(+) create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-3.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-4.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-6.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-7.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess.php diff --git a/tests/PHPStan/Levels/LevelsIntegrationTest.php b/tests/PHPStan/Levels/LevelsIntegrationTest.php index 12499dce44..044a088f60 100644 --- a/tests/PHPStan/Levels/LevelsIntegrationTest.php +++ b/tests/PHPStan/Levels/LevelsIntegrationTest.php @@ -41,6 +41,7 @@ public static function dataTopics(): array ['arrayDestructuring'], ['listType'], ['missingTypes'], + ['arrayOffsetAccess'], ]; if (PHP_VERSION_ID >= 80300) { $topics[] = ['constantAccesses83']; diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-3.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-3.json new file mode 100644 index 0000000000..3e23caff09 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-3.json @@ -0,0 +1,7 @@ +[ + { + "message": "Invalid array key type DateTimeImmutable.", + "line": 17, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json new file mode 100644 index 0000000000..8ba1c517f1 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json @@ -0,0 +1,37 @@ +[ + { + "message": "Expression \"$a[42]\" on a separate line does not do anything.", + "line": 15, + "ignorable": true + }, + { + "message": "Expression \"$a[null]\" on a separate line does not do anything.", + "line": 16, + "ignorable": true + }, + { + "message": "Expression \"$a[$intOrNull]\" on a separate line does not do anything.", + "line": 18, + "ignorable": true + }, + { + "message": "Expression \"$a[$objectOrInt]\" on a separate line does not do anything.", + "line": 19, + "ignorable": true + }, + { + "message": "Expression \"$a[$objectOrNull]\" on a separate line does not do anything.", + "line": 20, + "ignorable": true + }, + { + "message": "Expression \"$a[$explicitlyMixed]\" on a separate line does not do anything.", + "line": 21, + "ignorable": true + }, + { + "message": "Expression \"$a[$implicitlyMixed]\" on a separate line does not do anything.", + "line": 22, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json new file mode 100644 index 0000000000..c8bf2976e5 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json @@ -0,0 +1,12 @@ +[ + { + "message": "Method Levels\\ArrayOffsetAccess\\Foo::test() has parameter $a with no value type specified in iterable type array.", + "line": 7, + "ignorable": true + }, + { + "message": "Method Levels\\ArrayOffsetAccess\\Foo::test() has parameter $implicitlyMixed with no type specified.", + "line": 7, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json new file mode 100644 index 0000000000..2df318590a --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json @@ -0,0 +1,12 @@ +[ + { + "message": "Possibly invalid array key type int|object.", + "line": 19, + "ignorable": true + }, + { + "message": "Possibly invalid array key type object|null.", + "line": 20, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess.php b/tests/PHPStan/Levels/data/arrayOffsetAccess.php new file mode 100644 index 0000000000..a0f58dff96 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess.php @@ -0,0 +1,24 @@ + Date: Sun, 27 Jul 2025 00:53:53 +0200 Subject: [PATCH 2/4] Fix InvalidKeyInArrayDimFetchRule --- .../Arrays/InvalidKeyInArrayDimFetchRule.php | 18 +++++++++++------- .../data/arrayOffsetAccess-10-missing.json | 7 +++++++ .../Levels/data/arrayOffsetAccess-10.json | 7 +++++++ .../Levels/data/arrayOffsetAccess-7.json | 2 +- .../data/arrayOffsetAccess-8-missing.json | 7 +++++++ .../Levels/data/arrayOffsetAccess-8.json | 7 +++++++ .../data/arrayOffsetAccess-9-missing.json | 7 +++++++ .../Levels/data/arrayOffsetAccess-9.json | 7 +++++++ 8 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-10.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-8.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json create mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-9.json diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index 30f7febe49..e90c9bfe9e 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -10,7 +10,6 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Type\ErrorType; -use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -41,22 +40,27 @@ public function processNode(Node $node, Scope $scope): array return []; } - $dimensionType = $scope->getType($node->dim); - if ($dimensionType instanceof MixedType) { - return []; - } - $varType = $this->ruleLevelHelper->findTypeToCheck( $scope, $node->var, '', - static fn (Type $varType): bool => $varType->isArray()->no() || AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType)->yes(), + static fn (Type $varType): bool => $varType->isArray()->no(), )->getType(); if ($varType instanceof ErrorType || $varType->isArray()->no()) { return []; } + $dimensionType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->dim, + '', + static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimType)->yes(), + )->getType(); + if ($dimensionType instanceof ErrorType) { + return []; + } + $isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); if ($isSuperType->yes() || ($isSuperType->maybe() && !$this->reportMaybes)) { return []; diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json new file mode 100644 index 0000000000..e31aa23936 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json @@ -0,0 +1,7 @@ +[ + { + "message": "Invalid array key type object.", + "line": 20, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-10.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-10.json new file mode 100644 index 0000000000..80b743c66c --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-10.json @@ -0,0 +1,7 @@ +[ + { + "message": "Possibly invalid array key type mixed.", + "line": 22, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json index 2df318590a..b80a88d7c0 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json @@ -5,7 +5,7 @@ "ignorable": true }, { - "message": "Possibly invalid array key type object|null.", + "message": "Invalid array key type object.", "line": 20, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json new file mode 100644 index 0000000000..e31aa23936 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json @@ -0,0 +1,7 @@ +[ + { + "message": "Invalid array key type object.", + "line": 20, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-8.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-8.json new file mode 100644 index 0000000000..910aa0e6b1 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-8.json @@ -0,0 +1,7 @@ +[ + { + "message": "Possibly invalid array key type object|null.", + "line": 20, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json new file mode 100644 index 0000000000..e31aa23936 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json @@ -0,0 +1,7 @@ +[ + { + "message": "Invalid array key type object.", + "line": 20, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-9.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-9.json new file mode 100644 index 0000000000..50cc3c7bbb --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-9.json @@ -0,0 +1,7 @@ +[ + { + "message": "Possibly invalid array key type mixed.", + "line": 21, + "ignorable": true + } +] \ No newline at end of file From 38c316f122cede93b6221aed9298032c6a108910 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 27 Jul 2025 01:06:10 +0200 Subject: [PATCH 3/4] Fix RuleLevelHelper --- src/Rules/RuleLevelHelper.php | 6 +++++- .../PHPStan/Levels/data/arrayOffsetAccess-10-missing.json | 7 ------- tests/PHPStan/Levels/data/arrayOffsetAccess-6.json | 2 +- tests/PHPStan/Levels/data/arrayOffsetAccess-7.json | 2 +- tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json | 7 ------- tests/PHPStan/Levels/data/arrayOffsetAccess-8.json | 7 ------- tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json | 7 ------- 7 files changed, 7 insertions(+), 31 deletions(-) delete mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json delete mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json delete mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-8.json delete mode 100644 tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 109b30a2b3..d82ff0745b 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -190,7 +190,11 @@ private function findTypeToCheckImplementation( bool $isTopLevel = false, ): FoundTypeResult { - if (!$this->checkNullables && !$type->isNull()->yes()) { + if ( + !$this->checkNullables + && !$type->isNull()->yes() + && !$unionTypeCriteriaCallback(new NullType()) + ) { $type = TypeCombinator::removeNull($type); } diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json deleted file mode 100644 index e31aa23936..0000000000 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-10-missing.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Invalid array key type object.", - "line": 20, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json index c8bf2976e5..0033cdbfde 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json @@ -9,4 +9,4 @@ "line": 7, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json index b80a88d7c0..2df318590a 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json @@ -5,7 +5,7 @@ "ignorable": true }, { - "message": "Invalid array key type object.", + "message": "Possibly invalid array key type object|null.", "line": 20, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json deleted file mode 100644 index e31aa23936..0000000000 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-8-missing.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Invalid array key type object.", - "line": 20, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-8.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-8.json deleted file mode 100644 index 910aa0e6b1..0000000000 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-8.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Possibly invalid array key type object|null.", - "line": 20, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json deleted file mode 100644 index e31aa23936..0000000000 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-9-missing.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Invalid array key type object.", - "line": 20, - "ignorable": true - } -] \ No newline at end of file From 863021191997709f55eca92fe2d89bd8655e77f7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 27 Jul 2025 01:18:50 +0200 Subject: [PATCH 4/4] Fix lint --- .../Levels/data/arrayOffsetAccess-6.json | 4 ++-- .../PHPStan/Levels/data/arrayOffsetAccess.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json index 0033cdbfde..1e3cefa62e 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json @@ -1,12 +1,12 @@ [ { "message": "Method Levels\\ArrayOffsetAccess\\Foo::test() has parameter $a with no value type specified in iterable type array.", - "line": 7, + "line": 13, "ignorable": true }, { "message": "Method Levels\\ArrayOffsetAccess\\Foo::test() has parameter $implicitlyMixed with no type specified.", - "line": 7, + "line": 13, "ignorable": true } ] diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess.php b/tests/PHPStan/Levels/data/arrayOffsetAccess.php index a0f58dff96..cff9bd9041 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess.php +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess.php @@ -3,15 +3,15 @@ namespace Levels\ArrayOffsetAccess; class Foo { - /** @return void */ - public function test( - array $a, - int|null $intOrNull, - object|int $objectOrInt, - object|null $objectOrNull, - mixed $explicitlyMixed, - $implicitlyMixed, - ) { + /** + * @param int|null $intOrNull + * @param object|int $objectOrInt + * @param object|null $objectOrNull + * @param mixed $explicitlyMixed + * @return void + */ + public function test(array $a, $intOrNull, $objectOrInt, $objectOrNull, $explicitlyMixed, $implicitlyMixed) + { $a[42]; $a[null]; $a[new \DateTimeImmutable()];