From 9f3a13d944d209694def72e2bea0f046c324a27f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 26 Jul 2025 15:21:02 +0200 Subject: [PATCH 1/5] Add non regression test --- .../NonexistentOffsetInArrayDimFetchRuleTest.php | 12 ++++++++++++ tests/PHPStan/Rules/Arrays/data/bug-1061.php | 15 +++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-1061.php diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 3d75252f27..e3c2ae5fc5 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -954,4 +954,16 @@ public function testBug12447(): void ]); } + public function testBug1061(): void + { + $this->reportPossiblyNonexistentConstantArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-1061.php'], [ + [ + "Offset 'one'|'two' might not exist on array{two: 1, three: 2}.", + 14, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-1061.php b/tests/PHPStan/Rules/Arrays/data/bug-1061.php new file mode 100644 index 0000000000..2513b4d498 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-1061.php @@ -0,0 +1,15 @@ + 1, + "three" => 2 + ]; +} + +foreach (A::KEYS as $key) { + echo A::ARR[$key]; +} From bac5da80448a652bb857111cc3b04b93c4d2a439 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 26 Jul 2025 17:10:51 +0200 Subject: [PATCH 2/5] Rework --- .../NonexistentOffsetInArrayDimFetchCheck.php | 9 ++++++--- ...onexistentOffsetInArrayDimFetchRuleTest.php | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index 872211ec24..c567524b4c 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -76,6 +76,7 @@ public function check( } else { $flattenedTypes = TypeUtils::flattenTypes($type); } + foreach ($flattenedTypes as $innerType) { if ( $this->reportPossiblyNonexistentGeneralArrayOffset @@ -95,9 +96,11 @@ public function check( break; } if ($dimType instanceof UnionType) { - if ($innerType->hasOffsetValueType($dimType)->no()) { - $report = true; - break; + foreach ($dimType->getTypes() as $subDimType) { + if ($innerType->hasOffsetValueType($subDimType)->no()) { + $report = true; + break 2; + } } continue; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index e3c2ae5fc5..619453c659 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -119,6 +119,10 @@ public function testRule(): void 'Cannot access offset \'a\' on array{a: 1, b: 1}|(Closure(): void).', 258, ], + [ + 'Offset int|null might not exist on array.', + 309, + ], [ 'Offset null does not exist on array.', 310, @@ -127,6 +131,10 @@ public function testRule(): void 'Offset int does not exist on array.', 312, ], + [ + ' Offset int|null might not exist on array.', + 314, + ], [ 'Offset \'baz\' might not exist on array{bar: 1, baz?: 2}.', 344, @@ -185,6 +193,10 @@ public function testStrings(): void 'Offset 12.34 does not exist on \'foo\'.', 13, ], + [ + "Offset int|object might not exist on 'foo'.", + 16, + ], [ 'Offset \'foo\' might not exist on array|string.', 24, @@ -193,6 +205,10 @@ public function testStrings(): void 'Offset 12.34 might not exist on array|string.', 28, ], + [ + 'Offset int|object might not exist on array|string.', + 32, + ], ]); } @@ -956,8 +972,6 @@ public function testBug12447(): void public function testBug1061(): void { - $this->reportPossiblyNonexistentConstantArrayOffset = true; - $this->analyse([__DIR__ . '/data/bug-1061.php'], [ [ "Offset 'one'|'two' might not exist on array{two: 1, three: 2}.", From b458c1a949faf87b4a5cd9b03df369c68a6e29f3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 26 Jul 2025 17:32:29 +0200 Subject: [PATCH 3/5] Fix --- .../NonexistentOffsetInArrayDimFetchCheck.php | 19 ++++++++----------- ...nexistentOffsetInArrayDimFetchRuleTest.php | 10 +--------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index c567524b4c..c46a62264e 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -14,7 +14,6 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\Type; use PHPStan\Type\TypeUtils; -use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function count; use function sprintf; @@ -95,17 +94,15 @@ public function check( $report = true; break; } - if ($dimType instanceof UnionType) { - foreach ($dimType->getTypes() as $subDimType) { - if ($innerType->hasOffsetValueType($subDimType)->no()) { - $report = true; - break 2; - } - } - continue; + if ($dimType instanceof BenevolentUnionType) { + $flattenedInnerTypes = [$dimType]; + } else { + $flattenedInnerTypes = TypeUtils::flattenTypes($dimType); } - foreach (TypeUtils::flattenTypes($dimType) as $innerDimType) { - if ($innerType->hasOffsetValueType($innerDimType)->no()) { + foreach ($flattenedInnerTypes as $innerDimType) { + if ( + $innerType->hasOffsetValueType($innerDimType)->no() + ) { $report = true; break 2; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 619453c659..b135c214ff 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -132,7 +132,7 @@ public function testRule(): void 312, ], [ - ' Offset int|null might not exist on array.', + 'Offset int|null might not exist on array.', 314, ], [ @@ -193,10 +193,6 @@ public function testStrings(): void 'Offset 12.34 does not exist on \'foo\'.', 13, ], - [ - "Offset int|object might not exist on 'foo'.", - 16, - ], [ 'Offset \'foo\' might not exist on array|string.', 24, @@ -205,10 +201,6 @@ public function testStrings(): void 'Offset 12.34 might not exist on array|string.', 28, ], - [ - 'Offset int|object might not exist on array|string.', - 32, - ], ]); } From 38b1efadab52de82c26fb0147ae42a7da95e87f3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 1 Aug 2025 13:06:53 +0200 Subject: [PATCH 4/5] Fix tests --- .../PHPStan/Levels/data/stringOffsetAccess-7.json | 15 +++++++++++++++ .../NonexistentOffsetInArrayDimFetchRuleTest.php | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/tests/PHPStan/Levels/data/stringOffsetAccess-7.json b/tests/PHPStan/Levels/data/stringOffsetAccess-7.json index 5471fbcf70..1b23f1ed6c 100644 --- a/tests/PHPStan/Levels/data/stringOffsetAccess-7.json +++ b/tests/PHPStan/Levels/data/stringOffsetAccess-7.json @@ -1,4 +1,9 @@ [ + { + "message": "Offset int|object might not exist on 'foo'.", + "line": 19, + "ignorable": true + }, { "message": "Offset 'foo' might not exist on array|string.", "line": 27, @@ -9,11 +14,21 @@ "line": 31, "ignorable": true }, + { + "message": "Offset int|object might not exist on array|string.", + "line": 35, + "ignorable": true + }, { "message": "Possibly invalid array key type int|object.", "line": 35, "ignorable": true }, + { + "message": "Offset int|object might not exist on array{baz: 21}|array{foo: 17, bar: 19}.", + "line": 55, + "ignorable": true + }, { "message": "Possibly invalid array key type int|object.", "line": 55, diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index b135c214ff..402b78a36d 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -193,6 +193,10 @@ public function testStrings(): void 'Offset 12.34 does not exist on \'foo\'.', 13, ], + [ + 'Offset int|object might not exist on \'foo\'.', + 16, + ], [ 'Offset \'foo\' might not exist on array|string.', 24, @@ -201,6 +205,10 @@ public function testStrings(): void 'Offset 12.34 might not exist on array|string.', 28, ], + [ + 'Offset int|object might not exist on array|string.', + 32, + ], ]); } From ccea22ab0f9875a26278458f68718d3edee6a86b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Sep 2025 10:02:24 +0200 Subject: [PATCH 5/5] Update test --- tests/PHPStan/Levels/data/stringOffsetAccess-7.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/PHPStan/Levels/data/stringOffsetAccess-7.json b/tests/PHPStan/Levels/data/stringOffsetAccess-7.json index 1b23f1ed6c..cfe8c12f38 100644 --- a/tests/PHPStan/Levels/data/stringOffsetAccess-7.json +++ b/tests/PHPStan/Levels/data/stringOffsetAccess-7.json @@ -24,11 +24,6 @@ "line": 35, "ignorable": true }, - { - "message": "Offset int|object might not exist on array{baz: 21}|array{foo: 17, bar: 19}.", - "line": 55, - "ignorable": true - }, { "message": "Possibly invalid array key type int|object.", "line": 55,