From 23392bb91833a9b412e054615428eb455d6ca86b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 09:25:36 +0200 Subject: [PATCH 1/7] Added regression tests --- tests/PHPStan/Analyser/nsrt/bug-10640.php | 25 +++++++++ tests/PHPStan/Analyser/nsrt/bug-12078.php | 67 +++++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-2294.php | 20 +++++++ tests/PHPStan/Analyser/nsrt/bug-6173.php | 32 +++++++++++ tests/PHPStan/Analyser/nsrt/bug-8270.php | 29 ++++++++++ 5 files changed, 173 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10640.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12078.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-2294.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6173.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-8270.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10640.php b/tests/PHPStan/Analyser/nsrt/bug-10640.php new file mode 100644 index 0000000000..bd69320d04 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10640.php @@ -0,0 +1,25 @@ +, 1>}>', $changes); + +foreach (toRem() as $del) { + $changes[$add['id']]['del'][] = doSomething($del); +} +assertType('array, 1>, del?: non-empty-array, 2>}>', $changes); + +foreach ($changes as $changeSet) { + if (isset($changeSet['del'])) { + doDel($changeSet['del']); + } + if (isset($changeSet['add'])) { + doAdd($changeSet['add']); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-12078.php b/tests/PHPStan/Analyser/nsrt/bug-12078.php new file mode 100644 index 0000000000..178f31a693 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12078.php @@ -0,0 +1,67 @@ + + */ +function returnsData6M(): array +{ + return ["A" => 'data A', "B" => 'Data B']; +} + +/** + * @return array + */ +function returnsData3M(): array +{ + return ["A" => 'data A', "C" => 'Data C']; +} + +function main() +{ + $arrDataByKey = []; + + $arrData6M = returnsData6M(); + if ([] === $arrData6M) { + echo "No data for 6M\n"; + } else { + foreach ($arrData6M as $key => $data) { + $arrDataByKey[$key]['6M'][] = $data; + } + } + + $arrData3M = returnsData3M(); + if ([] === $arrData3M) { + echo "No data for 3M\n"; + } else { + foreach ($arrData3M as $key => $data) { + $arrDataByKey[$key]['3M'][] = $data; + } + } + /* + So $arrDataByKey looks like + [ + 'A'=>[ + '6M'=>['data A'], + '3M'=>['data A'] + ], + 'B'=>[ + '6M'=>['data B'] + ], + 'C'=>[ + '3M'=>['data C'] + ] + ] + */ + + assertType("array, '3M'?: non-empty-list}>", $arrDataByKey); + foreach ($arrDataByKey as $key => $arrDataByKeyForKey) { + assertType("array, '3M'?: non-empty-list}>", $arrDataByKeyForKey); + echo [] === ($arrDataByKeyForKey['6M'] ?? []) ? 'No 6M data for key ' . $key . "\n" : 'We got 6M data for key ' . $key . "\n"; + echo [] === ($arrDataByKeyForKey['3M'] ?? []) ? 'No 3M data for key ' . $key . "\n" : 'We got 3M data for key ' . $key . "\n"; + assertType("array, '3M'?: non-empty-list}>", $arrDataByKeyForKey); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-2294.php b/tests/PHPStan/Analyser/nsrt/bug-2294.php new file mode 100644 index 0000000000..2308d26974 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-2294.php @@ -0,0 +1,20 @@ + null, 'B' => null]; +foreach($entries as $key => $value) { + $entries[$key] = ['a' => 1, 'b' => 2]; +} +assertType('array{A: array{a: 1, b: 2}|null, B: array{a: 1, b: 2}|null}', $entries); +// Uncommenting the next line does NOT make the error go away +//$entries['A'] = ['a' => 1, 'b' => 2]; + +// Removing one of these lines also makes the error go away +$entries['A']['a'] += 1; +$entries['A']['b'] += 1; +assertType('array{A: array{a: 2, b: 3}, B: array{a: 1, b: 2}|null}', $entries); diff --git a/tests/PHPStan/Analyser/nsrt/bug-6173.php b/tests/PHPStan/Analyser/nsrt/bug-6173.php new file mode 100644 index 0000000000..41ced55a63 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6173.php @@ -0,0 +1,32 @@ +', $res); + foreach ($res as $id => $r) { + assertType('non-empty-array{foo?: int, bar?: int}', $r); + return isset($r['foo']); + } + + return false; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-8270.php b/tests/PHPStan/Analyser/nsrt/bug-8270.php new file mode 100644 index 0000000000..937e1833f2 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8270.php @@ -0,0 +1,29 @@ + false, + 'value' => rand(), + ]; + } + assertType('list}>', $list); + + // TODO: sort list by value asc... + $k = array_key_first($list); + $list[$k]['test'] = true; // <--- assign only first item! + + foreach ($list as $item) { + if ($item['test']) { + echo $item['value']; + } + } + assertType('array{test: true, value?: int<0, max>}', $list); + +}; From 8a0ccaf412791c10d1471daed4193a572a763a6d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 15:34:04 +0200 Subject: [PATCH 2/7] Update bug-10640.php --- tests/PHPStan/Analyser/nsrt/bug-10640.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10640.php b/tests/PHPStan/Analyser/nsrt/bug-10640.php index bd69320d04..fb4af733a4 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10640.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10640.php @@ -23,3 +23,9 @@ doAdd($changeSet['add']); } } + +function doSomething($s) {} +function toAdd($s) {} +function toRem($s) {} +function doDel($s) {} +function doAdd($s) {} From 23295eefb42946cf59c0d75317460ccff78c1f01 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 15:37:14 +0200 Subject: [PATCH 3/7] fix --- tests/PHPStan/Analyser/nsrt/bug-10640.php | 4 ++-- tests/PHPStan/Analyser/nsrt/bug-12078.php | 6 +++--- tests/PHPStan/Analyser/nsrt/bug-8270.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10640.php b/tests/PHPStan/Analyser/nsrt/bug-10640.php index fb4af733a4..12452341c6 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10640.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10640.php @@ -8,12 +8,12 @@ foreach (toAdd() as $add) { $changes[$add['id']]['add'][] = doSomething($add); } -assertType('array, 1>}>', $changes); +assertType('array', $changes); foreach (toRem() as $del) { $changes[$add['id']]['del'][] = doSomething($del); } -assertType('array, 1>, del?: non-empty-array, 2>}>', $changes); +assertType('array', $changes); foreach ($changes as $changeSet) { if (isset($changeSet['del'])) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12078.php b/tests/PHPStan/Analyser/nsrt/bug-12078.php index 178f31a693..c1d22c1ab1 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12078.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12078.php @@ -57,11 +57,11 @@ function main() ] */ - assertType("array, '3M'?: non-empty-list}>", $arrDataByKey); + assertType("array, '3M'?: non-empty-list}>", $arrDataByKey); foreach ($arrDataByKey as $key => $arrDataByKeyForKey) { - assertType("array, '3M'?: non-empty-list}>", $arrDataByKeyForKey); + assertType("non-empty-array{'6M'?: non-empty-list, '3M'?: non-empty-list}", $arrDataByKeyForKey); echo [] === ($arrDataByKeyForKey['6M'] ?? []) ? 'No 6M data for key ' . $key . "\n" : 'We got 6M data for key ' . $key . "\n"; echo [] === ($arrDataByKeyForKey['3M'] ?? []) ? 'No 3M data for key ' . $key . "\n" : 'We got 3M data for key ' . $key . "\n"; - assertType("array, '3M'?: non-empty-list}>", $arrDataByKeyForKey); + assertType("non-empty-array{'6M'?: non-empty-list, '3M'?: non-empty-list}", $arrDataByKeyForKey); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-8270.php b/tests/PHPStan/Analyser/nsrt/bug-8270.php index 937e1833f2..6f90ca0225 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8270.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8270.php @@ -13,7 +13,7 @@ function (): void { 'value' => rand(), ]; } - assertType('list}>', $list); + assertType('non-empty-list}>', $list); // TODO: sort list by value asc... $k = array_key_first($list); @@ -24,6 +24,6 @@ function (): void { echo $item['value']; } } - assertType('array{test: true, value?: int<0, max>}', $list); + assertType('non-empty-list}>&hasOffsetValue(0, array{test: true, value: int<0, max>})', $list); }; From fe92101bff9b2ae7574b86c73ce40691e6e9ae11 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 15:40:27 +0200 Subject: [PATCH 4/7] Create bug-10025.php --- tests/PHPStan/Analyser/nsrt/bug-10025.php | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10025.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10025.php b/tests/PHPStan/Analyser/nsrt/bug-10025.php new file mode 100644 index 0000000000..4d172f2aac --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10025.php @@ -0,0 +1,36 @@ + $foos + * @param list $bars + */ +function x(array $foos, array $bars): void +{ + $arr = []; + foreach ($foos as $foo) { + $arr[$foo->groupId]['foo'][] = $foo; + } + foreach ($bars as $bar) { + $arr[$bar->groupId]['bar'][] = $bar; + } + + assertType('array, bar?: non-empty-list}>', $arr); + foreach ($arr as $groupId => $group) { + if (isset($group['foo'])) { + } + if (isset($group['bar'])) { + } + } + + assertType('array, bar?: non-empty-list}>', $arr); +} + From 6528bb5558162d10c825962a39091e635aba354d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 15:42:19 +0200 Subject: [PATCH 5/7] Delete tests/PHPStan/Analyser/nsrt/bug-8270.php --- tests/PHPStan/Analyser/nsrt/bug-8270.php | 29 ------------------------ 1 file changed, 29 deletions(-) delete mode 100644 tests/PHPStan/Analyser/nsrt/bug-8270.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-8270.php b/tests/PHPStan/Analyser/nsrt/bug-8270.php deleted file mode 100644 index 6f90ca0225..0000000000 --- a/tests/PHPStan/Analyser/nsrt/bug-8270.php +++ /dev/null @@ -1,29 +0,0 @@ - false, - 'value' => rand(), - ]; - } - assertType('non-empty-list}>', $list); - - // TODO: sort list by value asc... - $k = array_key_first($list); - $list[$k]['test'] = true; // <--- assign only first item! - - foreach ($list as $item) { - if ($item['test']) { - echo $item['value']; - } - } - assertType('non-empty-list}>&hasOffsetValue(0, array{test: true, value: int<0, max>})', $list); - -}; From 71d7f49d8062f2ddc2dc07f22105e33097458cb1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 15:43:44 +0200 Subject: [PATCH 6/7] Delete tests/PHPStan/Analyser/nsrt/bug-2294.php --- tests/PHPStan/Analyser/nsrt/bug-2294.php | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 tests/PHPStan/Analyser/nsrt/bug-2294.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-2294.php b/tests/PHPStan/Analyser/nsrt/bug-2294.php deleted file mode 100644 index 2308d26974..0000000000 --- a/tests/PHPStan/Analyser/nsrt/bug-2294.php +++ /dev/null @@ -1,20 +0,0 @@ - null, 'B' => null]; -foreach($entries as $key => $value) { - $entries[$key] = ['a' => 1, 'b' => 2]; -} -assertType('array{A: array{a: 1, b: 2}|null, B: array{a: 1, b: 2}|null}', $entries); -// Uncommenting the next line does NOT make the error go away -//$entries['A'] = ['a' => 1, 'b' => 2]; - -// Removing one of these lines also makes the error go away -$entries['A']['a'] += 1; -$entries['A']['b'] += 1; -assertType('array{A: array{a: 2, b: 3}, B: array{a: 1, b: 2}|null}', $entries); From 6a88a04be4842cbef1d57c7e3539270d4a0a6be8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 15:49:26 +0200 Subject: [PATCH 7/7] Update IssetRuleTest.php --- tests/PHPStan/Rules/Variables/IssetRuleTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index da52dc925f..40b837c134 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -504,4 +504,11 @@ public function testPr4374(): void ]); } + public function testBug10640(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10640.php'], []); + } + }