From 9e583a931b105cfd55f68eded7b3bfc5884d6502 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 28 Sep 2025 22:50:25 +0200 Subject: [PATCH] Fix --- .../NonexistentOffsetInArrayDimFetchCheck.php | 24 +++++++-- .../Levels/data/arrayOffsetAccess-3.json | 5 -- .../Levels/data/arrayOffsetAccess-7.json | 25 --------- .../Levels/data/stringOffsetAccess-3.json | 5 -- ...nexistentOffsetInArrayDimFetchRuleTest.php | 53 +++++++++++++++++-- tests/PHPStan/Rules/Arrays/data/pr-4385.php | 43 +++++++++++++++ 6 files changed, 111 insertions(+), 44 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/pr-4385.php diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index c46a62264e..efc11dc600 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -12,7 +12,9 @@ use PHPStan\Rules\RuleLevelHelper; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\ErrorType; +use PHPStan\Type\NeverType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use PHPStan\Type\VerbosityLevel; use function count; @@ -60,6 +62,14 @@ public function check( } if ($type->hasOffsetValueType($dimType)->no()) { + if ($type->isArray()->yes()) { + $validArrayDimType = TypeCombinator::intersect(AllowedArrayKeysTypes::getType(), $dimType); + if ($validArrayDimType instanceof NeverType) { + // Already reported by InvalidKeyInArrayDimFetchRule + return []; + } + } + return [ RuleErrorBuilder::message(sprintf('Offset %s does not exist on %s.', $dimType->describe(count($dimType->getConstantStrings()) > 0 ? VerbosityLevel::precise() : VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) ->identifier('offsetAccess.notFound') @@ -76,12 +86,16 @@ public function check( $flattenedTypes = TypeUtils::flattenTypes($type); } + $validArrayDimType = TypeCombinator::intersect(AllowedArrayKeysTypes::getType(), $dimType); + foreach ($flattenedTypes as $innerType) { + $dimTypeToCheck = $innerType->isArray()->yes() ? $validArrayDimType : $dimType; + if ( $this->reportPossiblyNonexistentGeneralArrayOffset && $innerType->isArray()->yes() && !$innerType->isConstantArray()->yes() - && !$innerType->hasOffsetValueType($dimType)->yes() + && !$innerType->hasOffsetValueType($dimTypeToCheck)->yes() ) { $report = true; break; @@ -89,15 +103,15 @@ public function check( if ( $this->reportPossiblyNonexistentConstantArrayOffset && $innerType->isConstantArray()->yes() - && !$innerType->hasOffsetValueType($dimType)->yes() + && !$innerType->hasOffsetValueType($dimTypeToCheck)->yes() ) { $report = true; break; } - if ($dimType instanceof BenevolentUnionType) { - $flattenedInnerTypes = [$dimType]; + if ($dimTypeToCheck instanceof BenevolentUnionType) { + $flattenedInnerTypes = [$dimTypeToCheck]; } else { - $flattenedInnerTypes = TypeUtils::flattenTypes($dimType); + $flattenedInnerTypes = TypeUtils::flattenTypes($dimTypeToCheck); } foreach ($flattenedInnerTypes as $innerDimType) { if ( diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-3.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-3.json index 3d9ed2b7e4..6e0ae5b84f 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-3.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-3.json @@ -3,10 +3,5 @@ "message": "Invalid array key type DateTimeImmutable.", "line": 17, "ignorable": true - }, - { - "message": "Offset DateTimeImmutable does not exist on array.", - "line": 17, - "ignorable": true } ] diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json index 3f715fbdc8..674057be06 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-7.json @@ -1,49 +1,24 @@ [ - { - "message": "Offset int|object might not exist on array.", - "line": 19, - "ignorable": true - }, { "message": "Possibly invalid array key type int|object.", "line": 19, "ignorable": true }, - { - "message": "Offset object|null might not exist on array.", - "line": 20, - "ignorable": true - }, { "message": "Possibly invalid array key type object|null.", "line": 20, "ignorable": true }, - { - "message": "Offset DateTimeImmutable might not exist on array|ArrayAccess.", - "line": 26, - "ignorable": true - }, { "message": "Possibly invalid array key type DateTimeImmutable.", "line": 26, "ignorable": true }, - { - "message": "Offset int|object might not exist on array|ArrayAccess.", - "line": 28, - "ignorable": true - }, { "message": "Possibly invalid array key type int|object.", "line": 28, "ignorable": true }, - { - "message": "Offset object|null might not exist on array|ArrayAccess.", - "line": 29, - "ignorable": true - }, { "message": "Possibly invalid array key type object|null.", "line": 29, diff --git a/tests/PHPStan/Levels/data/stringOffsetAccess-3.json b/tests/PHPStan/Levels/data/stringOffsetAccess-3.json index f7f4afafdd..2b54f201d4 100644 --- a/tests/PHPStan/Levels/data/stringOffsetAccess-3.json +++ b/tests/PHPStan/Levels/data/stringOffsetAccess-3.json @@ -18,10 +18,5 @@ "message": "Invalid array key type stdClass.", "line": 59, "ignorable": true - }, - { - "message": "Offset stdClass does not exist on array{baz: 21}|array{foo: 17, bar: 19}.", - "line": 59, - "ignorable": true } ] diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 0334660b40..1e0e2248eb 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -949,10 +949,6 @@ public function testBugObject(): void 'Offset int|object does not exist on array{baz: 21}|array{foo: 17, bar: 19}.', 12, ], - [ - 'Offset object does not exist on array.', - 21, - ], ]); } @@ -1049,6 +1045,55 @@ public function testBug13538(): void ]); } + public function testPR4385(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + $this->reportPossiblyNonexistentConstantArrayOffset = true; + + $this->analyse([__DIR__ . '/data/pr-4385.php'], [ + [ + 'Offset int might not exist on array.', + 24, + ], + [ + 'Offset string might not exist on array.', + 25, + ], + [ + 'Offset array|int might not exist on array.', + 28, + ], + [ + 'Offset array|string might not exist on array.', + 29, + ], + [ + 'Offset 0|array might not exist on array.', + 30, + ], + [ + 'Offset int might not exist on array{string}.', + 33, + ], + [ + 'Offset string might not exist on array{string}.', + 34, + ], + [ + 'Offset array|int might not exist on array{string}.', + 37, + ], + [ + 'Offset array|string might not exist on array{string}.', + 38, + ], + [ + 'Offset array|int might not exist on array|string.', + 41, + ], + ]); + } + public function testBug12805(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/pr-4385.php b/tests/PHPStan/Rules/Arrays/data/pr-4385.php new file mode 100644 index 0000000000..073ea4d476 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/pr-4385.php @@ -0,0 +1,43 @@ + $array + * @param int $int + * @param string $string + * @param object $object + * @param array{0: string} $constantArray + * + * @return void + */ + public function test($array, $int, $string, $object, $constantArray) + { + $arrayOrObject = rand(0, 1) ? $array : $object; + $arrayOrInt = rand(0, 1) ? $array : $int; + $arrayOrString = rand(0, 1) ? $array : $string; + $arrayOrZero = rand(0, 1) ? $array : 0; + + $array[$array]; + $array[$int]; + $array[$string]; + $array[$object]; // Reported by InvalidKeyInArrayDimFetchRule + $array[$arrayOrObject]; // Reported by InvalidKeyInArrayDimFetchRule + $array[$arrayOrInt]; + $array[$arrayOrString]; + $array[$arrayOrZero]; + + $constantArray[$array]; + $constantArray[$int]; + $constantArray[$string]; + $constantArray[$object]; // Reported by InvalidKeyInArrayDimFetchRule + $constantArray[$arrayOrObject]; // Reported by InvalidKeyInArrayDimFetchRule + $constantArray[$arrayOrInt]; + $constantArray[$arrayOrString]; + $constantArray[$arrayOrZero]; // Reported by InvalidKeyInArrayDimFetchRule + + $arrayOrString[$arrayOrInt]; + } +}