From d1597941876588a1f10e10e46785594e5f88786b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 27 Jul 2025 00:22:04 +0200 Subject: [PATCH 1/8] 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/8] 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 90ef5c45c549d1a3c450086faea9242c4ee27f09 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 27 Jul 2025 01:06:10 +0200 Subject: [PATCH 3/8] 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 bc59757c1aeb9741c201ba0c91361bd730f075d4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 27 Jul 2025 01:18:50 +0200 Subject: [PATCH 4/8] 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()]; From 6f99f04c397e57edd81fcfc811a43aced9eac317 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 2 Sep 2025 17:15:22 +0200 Subject: [PATCH 5/8] Add more cases --- .../Levels/data/arrayOffsetAccess-4.json | 107 +++++++++++++++++- .../Levels/data/arrayOffsetAccess-6.json | 10 ++ .../Levels/data/arrayOffsetAccess-7.json | 17 ++- .../PHPStan/Levels/data/arrayOffsetAccess.php | 31 ++++- 4 files changed, 161 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json index 8ba1c517f1..3f91016ab3 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json @@ -33,5 +33,110 @@ "message": "Expression \"$a[$implicitlyMixed]\" on a separate line does not do anything.", "line": 22, "ignorable": true + }, + { + "message": "Expression \"$arrayOrObject[42]\" on a separate line does not do anything.", + "line": 24, + "ignorable": true + }, + { + "message": "Expression \"$arrayOrObject[null]\" on a separate line does not do anything.", + "line": 25, + "ignorable": true + }, + { + "message": "Expression \"$arrayOrObject[$intOrNull]\" on a separate line does not do anything.", + "line": 27, + "ignorable": true + }, + { + "message": "Expression \"$arrayOrObject[$objectOrInt]\" on a separate line does not do anything.", + "line": 28, + "ignorable": true + }, + { + "message": "Expression \"$arrayOrObject[$objectOrNull]\" on a separate line does not do anything.", + "line": 29, + "ignorable": true + }, + { + "message": "Expression \"$arrayOrObject[$explicitlyMixed]\" on a separate line does not do anything.", + "line": 30, + "ignorable": true + }, + { + "message": "Expression \"$arrayOrObject[$implicitlyMixed]\" on a separate line does not do anything.", + "line": 31, + "ignorable": true + }, + { + "message": "Expression \"$explicitlyMixed[42]\" on a separate line does not do anything.", + "line": 33, + "ignorable": true + }, + { + "message": "Expression \"$explicitlyMixed[null]\" on a separate line does not do anything.", + "line": 34, + "ignorable": true + }, + { + "message": "Expression \"$explicitlyMixed[$intOrNull]\" on a separate line does not do anything.", + "line": 36, + "ignorable": true + }, + { + "message": "Expression \"$explicitlyMixed[$objectOrInt]\" on a separate line does not do anything.", + "line": 37, + "ignorable": true + }, + { + "message": "Expression \"$explicitlyMixed[$objectOrNull]\" on a separate line does not do anything.", + "line": 38, + "ignorable": true + }, + { + "message": "Expression \"$explicitlyMixed[$explicitlyMixed]\" on a separate line does not do anything.", + "line": 39, + "ignorable": true + }, + { + "message": "Expression \"$explicitlyMixed[$implicitlyMixed]\" on a separate line does not do anything.", + "line": 40, + "ignorable": true + }, + { + "message": "Expression \"$implicitlyMixed[42]\" on a separate line does not do anything.", + "line": 42, + "ignorable": true + }, + { + "message": "Expression \"$implicitlyMixed[null]\" on a separate line does not do anything.", + "line": 43, + "ignorable": true + }, + { + "message": "Expression \"$implicitlyMixed[$intOrNull]\" on a separate line does not do anything.", + "line": 45, + "ignorable": true + }, + { + "message": "Expression \"$implicitlyMixed[$objectOrInt]\" on a separate line does not do anything.", + "line": 46, + "ignorable": true + }, + { + "message": "Expression \"$implicitlyMixed[$objectOrNull]\" on a separate line does not do anything.", + "line": 47, + "ignorable": true + }, + { + "message": "Expression \"$implicitlyMixed[$explicitlyMixed]\" on a separate line does not do anything.", + "line": 48, + "ignorable": true + }, + { + "message": "Expression \"$implicitlyMixed[$implicitlyMixed]\" on a separate line does not do anything.", + "line": 49, + "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 1e3cefa62e..5c3bc3d299 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-6.json @@ -4,6 +4,16 @@ "line": 13, "ignorable": true }, + { + "message": "Method Levels\\ArrayOffsetAccess\\Foo::test() has parameter $arrayOrObject with generic interface ArrayAccess but does not specify its types: TKey, TValue", + "line": 13, + "ignorable": true + }, + { + "message": "Method Levels\\ArrayOffsetAccess\\Foo::test() has parameter $arrayOrObject with no value type specified in iterable type array.", + "line": 13, + "ignorable": true + }, { "message": "Method Levels\\ArrayOffsetAccess\\Foo::test() has parameter $implicitlyMixed with no type specified.", "line": 13, diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json index 2df318590a..072b72e8cf 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json @@ -8,5 +8,20 @@ "message": "Possibly invalid array key type object|null.", "line": 20, "ignorable": true + }, + { + "message": "Invalid array key type DateTimeImmutable.", + "line": 26, + "ignorable": true + }, + { + "message": "Possibly invalid array key type int|object.", + "line": 28, + "ignorable": true + }, + { + "message": "Possibly invalid array key type object|null.", + "line": 29, + "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess.php b/tests/PHPStan/Levels/data/arrayOffsetAccess.php index cff9bd9041..362bb5c03f 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess.php +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess.php @@ -4,13 +4,13 @@ class Foo { /** + * @param array|\ArrayAccess $arrayOrObject * @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) + public function test(array $a, $arrayOrObject, $intOrNull, $objectOrInt, $objectOrNull, $explicitlyMixed, $implicitlyMixed): void { $a[42]; $a[null]; @@ -20,5 +20,32 @@ public function test(array $a, $intOrNull, $objectOrInt, $objectOrNull, $explici $a[$objectOrNull]; $a[$explicitlyMixed]; $a[$implicitlyMixed]; + + $arrayOrObject[42]; + $arrayOrObject[null]; + $arrayOrObject[new \DateTimeImmutable()]; + $arrayOrObject[$intOrNull]; + $arrayOrObject[$objectOrInt]; + $arrayOrObject[$objectOrNull]; + $arrayOrObject[$explicitlyMixed]; + $arrayOrObject[$implicitlyMixed]; + + $explicitlyMixed[42]; + $explicitlyMixed[null]; + $explicitlyMixed[new \DateTimeImmutable()]; + $explicitlyMixed[$intOrNull]; + $explicitlyMixed[$objectOrInt]; + $explicitlyMixed[$objectOrNull]; + $explicitlyMixed[$explicitlyMixed]; + $explicitlyMixed[$implicitlyMixed]; + + $implicitlyMixed[42]; + $implicitlyMixed[null]; + $implicitlyMixed[new \DateTimeImmutable()]; + $implicitlyMixed[$intOrNull]; + $implicitlyMixed[$objectOrInt]; + $implicitlyMixed[$objectOrNull]; + $implicitlyMixed[$explicitlyMixed]; + $implicitlyMixed[$implicitlyMixed]; } } From 7e0f73b1e2b4001c649550e64662df6a9a26f5e8 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 2 Sep 2025 17:48:31 +0200 Subject: [PATCH 6/8] Improve --- src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php | 13 +++++++++++-- tests/PHPStan/Levels/data/arrayOffsetAccess-7.json | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index e90c9bfe9e..5ba1a88009 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -47,7 +47,12 @@ public function processNode(Node $node, Scope $scope): array static fn (Type $varType): bool => $varType->isArray()->no(), )->getType(); - if ($varType instanceof ErrorType || $varType->isArray()->no()) { + if ($varType instanceof ErrorType) { + return []; + } + + $isArray = $varType->isArray(); + if ($isArray->no() || ($isArray->maybe() && !$this->reportMaybes)) { return []; } @@ -68,7 +73,11 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message( - sprintf('%s array key type %s.', $isSuperType->no() ? 'Invalid' : 'Possibly invalid', $dimensionType->describe(VerbosityLevel::typeOnly())), + sprintf( + '%s array key type %s.', + $isArray->yes() && $isSuperType->no() ? 'Invalid' : 'Possibly invalid', + $dimensionType->describe(VerbosityLevel::typeOnly()), + ), )->identifier('offsetAccess.invalidOffset')->build(), ]; } diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json index 072b72e8cf..674057be06 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json @@ -10,7 +10,7 @@ "ignorable": true }, { - "message": "Invalid array key type DateTimeImmutable.", + "message": "Possibly invalid array key type DateTimeImmutable.", "line": 26, "ignorable": true }, From 03b04f54470da0776a330b0df69fff6f9642503a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 2 Sep 2025 17:58:59 +0200 Subject: [PATCH 7/8] Fix level 9 --- .../Levels/data/arrayOffsetAccess-9.json | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-9.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-9.json index 50cc3c7bbb..edca311940 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-9.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-9.json @@ -3,5 +3,50 @@ "message": "Possibly invalid array key type mixed.", "line": 21, "ignorable": true + }, + { + "message": "Possibly invalid array key type mixed.", + "line": 30, + "ignorable": true + }, + { + "message": "Cannot access offset 42 on mixed.", + "line": 33, + "ignorable": true + }, + { + "message": "Cannot access offset null on mixed.", + "line": 34, + "ignorable": true + }, + { + "message": "Cannot access offset DateTimeImmutable on mixed.", + "line": 35, + "ignorable": true + }, + { + "message": "Cannot access offset int|null on mixed.", + "line": 36, + "ignorable": true + }, + { + "message": "Cannot access offset int|object on mixed.", + "line": 37, + "ignorable": true + }, + { + "message": "Cannot access offset object|null on mixed.", + "line": 38, + "ignorable": true + }, + { + "message": "Cannot access offset mixed on mixed.", + "line": 39, + "ignorable": true + }, + { + "message": "Cannot access offset mixed on mixed.", + "line": 40, + "ignorable": true } -] \ No newline at end of file +] From 9628a156de2d047409f061fc344c63ea11c54835 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 2 Sep 2025 18:05:17 +0200 Subject: [PATCH 8/8] Fix level 10 --- .../Levels/data/arrayOffsetAccess-10.json | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-10.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-10.json index 80b743c66c..e0b5d1d2d8 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-10.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-10.json @@ -3,5 +3,50 @@ "message": "Possibly invalid array key type mixed.", "line": 22, "ignorable": true + }, + { + "message": "Possibly invalid array key type mixed.", + "line": 31, + "ignorable": true + }, + { + "message": "Cannot access offset 42 on mixed.", + "line": 42, + "ignorable": true + }, + { + "message": "Cannot access offset null on mixed.", + "line": 43, + "ignorable": true + }, + { + "message": "Cannot access offset DateTimeImmutable on mixed.", + "line": 44, + "ignorable": true + }, + { + "message": "Cannot access offset int|null on mixed.", + "line": 45, + "ignorable": true + }, + { + "message": "Cannot access offset int|object on mixed.", + "line": 46, + "ignorable": true + }, + { + "message": "Cannot access offset object|null on mixed.", + "line": 47, + "ignorable": true + }, + { + "message": "Cannot access offset mixed on mixed.", + "line": 48, + "ignorable": true + }, + { + "message": "Cannot access offset mixed on mixed.", + "line": 49, + "ignorable": true } -] \ No newline at end of file +]