From b3fe1bbac8c52ff36415745f7d57dbe788019ff3 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 19 May 2026 05:35:09 +0000 Subject: [PATCH] Skip "might not exist" report for string types with integer offsets in union decomposition check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - In NonexistentOffsetInArrayDimFetchCheck, the reportMaybes decomposition loop flattens union types and checks each member individually. When a constant string member (e.g. '') returns "no" for an integer offset (e.g. 0), it incorrectly triggers "might not exist" even though the union-level check correctly returns "maybe". - Skip the report when innerType is a string and innerDimType is an integer, since string offset validity is length-dependent and users typically guard with length checks through variables that PHPStan cannot track. - The fix does not affect: definite "does not exist" errors (caught earlier), non-integer offsets on strings (objects, floats, strings still report), or array offset checks (completely unaffected). - Probed analogous cases: non-integer dim types on strings, array offset decomposition, ArrayDestructuringRule (shares the same check) — all already correct. --- .../NonexistentOffsetInArrayDimFetchCheck.php | 3 +++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 5 +++++ tests/PHPStan/Rules/Arrays/data/bug-13688.php | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-13688.php diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index 263a62aebf9..08e0794d0ca 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -126,6 +126,9 @@ public function check( if ( $innerType->hasOffsetValueType($innerDimType)->no() ) { + if ($innerType->isString()->yes() && $innerDimType->isInteger()->yes()) { + continue; + } $report = true; break 2; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 31570cfb12d..fc2dd408736 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -1326,6 +1326,11 @@ public function testBug9240(): void $this->analyse([__DIR__ . '/data/bug-9240.php'], []); } + public function testBug13688(): void + { + $this->analyse([__DIR__ . '/data/bug-13688.php'], []); + } + #[RequiresPhp('>= 8.4.0')] public function testArrayFindKeyExisting(): void { diff --git a/tests/PHPStan/Rules/Arrays/data/bug-13688.php b/tests/PHPStan/Rules/Arrays/data/bug-13688.php new file mode 100644 index 00000000000..991ff196a36 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-13688.php @@ -0,0 +1,19 @@ + 0 && $input[$inputLen-1] === ':'; + echo $hasTrailingColon ? "{$input} has trailing colon\n" : "{$input} does not have trailing colon\n"; +} + +function directComparison(): void +{ + /** @var 'a'|'abc' $s */ + $s = 'a'; + echo $s[0]; +}