Skip to content

Commit 2576141

Browse files
phpstan-botclaude
andcommitted
Merge test files and add reviewer's constant array test case
- Merge bug-1311-constant-array.php into bug-1311.php (single file, single namespace) - Merge bug-1940-with-key.php into bug-1940.php (single file, single namespace) - Add test for constant array by-ref foreach with sub-element modification to verify no false positive about missing offset after mutations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c1924d6 commit 2576141

4 files changed

Lines changed: 168 additions & 168 deletions

File tree

tests/PHPStan/Analyser/nsrt/bug-1311-constant-array.php

Lines changed: 0 additions & 99 deletions
This file was deleted.

tests/PHPStan/Analyser/nsrt/bug-1311.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,124 @@ public function convertList(array $temp): void
2424

2525
$this->list = $temp;
2626
}
27+
28+
public function convertListByRefWithoutKey(): void
29+
{
30+
$temp = [1, 2, 3];
31+
32+
foreach ($temp as &$item) {
33+
$item = (string) $item;
34+
}
35+
36+
assertType("array{'1'|'2'|'3', '1'|'2'|'3', '1'|'2'|'3'}", $temp);
37+
38+
$this->list = $temp;
39+
}
40+
41+
public function convertListByRefWithKey(): void
42+
{
43+
$temp = [1, 2, 3];
44+
45+
foreach ($temp as $k => &$item) {
46+
$item = (string) $item;
47+
}
48+
49+
assertType("array{'1'|'2'|'3', '1'|'2'|'3', '1'|'2'|'3'}", $temp);
50+
51+
$this->list = $temp;
52+
}
53+
54+
public function byRefConstantArrayConditional(): void
55+
{
56+
$temp = [1, 2, 3];
57+
58+
foreach ($temp as &$item) {
59+
if (rand(0, 1)) {
60+
$item = (string) $item;
61+
}
62+
}
63+
64+
assertType("array{1|2|3|'1'|'2'|'3', 1|2|3|'1'|'2'|'3', 1|2|3|'1'|'2'|'3'}", $temp);
65+
}
66+
67+
public function byRefConstantArrayWithBreak(): void
68+
{
69+
$temp = [1, 2, 3];
70+
71+
foreach ($temp as &$item) {
72+
$item = (string) $item;
73+
if (rand(0, 1)) {
74+
break;
75+
}
76+
}
77+
78+
assertType('array{1, 2, 3}', $temp);
79+
}
80+
81+
public function byRefConstantArrayIntval(): void
82+
{
83+
$temp = ['a', 'b', 'c'];
84+
85+
foreach ($temp as &$item) {
86+
$item = strlen($item);
87+
}
88+
89+
assertType('array{1, 1, 1}', $temp);
90+
}
91+
92+
public function byRefConstantArrayStringKeys(): void
93+
{
94+
$temp = ['x' => 1, 'y' => 2];
95+
96+
foreach ($temp as &$v) {
97+
$v = (string) $v;
98+
}
99+
100+
assertType("array{x: '1'|'2', y: '1'|'2'}", $temp);
101+
}
102+
103+
public function byRefConstantArrayNoOverwrite(): void
104+
{
105+
$temp = [1, 2, 3];
106+
107+
foreach ($temp as &$item) {
108+
echo $item;
109+
}
110+
111+
assertType('array{1, 2, 3}', $temp);
112+
}
113+
114+
public function constantArrayByRefSubElement(): void
115+
{
116+
$a = [
117+
[
118+
'one' => 'one',
119+
'two' => 'two',
120+
],
121+
[
122+
'one' => 'one',
123+
],
124+
];
125+
126+
foreach ($a as &$testArray) {
127+
$testArray['two'] = 'two';
128+
}
129+
unset($testArray);
130+
131+
assertType("array{array{one: 'one', two: 'two'}, array{one: 'one', two: 'two'}}", $a);
132+
133+
$key = 'three';
134+
135+
foreach ($a as $offset => $testArray) {
136+
$a[$offset][$key] = $key;
137+
}
138+
139+
assertType("array{array{one: 'one', two: 'two', three: 'three'}, array{one: 'one', two: 'two', three: 'three'}}", $a);
140+
141+
foreach ($a as $testArray) {
142+
assertType("array{one: 'one', two: 'two', three: 'three'}", $testArray);
143+
$testArray['two'];
144+
$testArray['three'];
145+
}
146+
}
27147
}

tests/PHPStan/Analyser/nsrt/bug-1940-with-key.php

Lines changed: 0 additions & 59 deletions
This file was deleted.

tests/PHPStan/Analyser/nsrt/bug-1940.php

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ function byRefWithoutKeyNonEmpty(array $arr): void
8383
}
8484

8585
/**
86-
* By-ref without key with break — should NOT rewrite since not all elements may be visited.
8786
* @param array<int, string> $arr
8887
*/
8988
function byRefWithoutKeyBreak(array $arr): void
@@ -99,7 +98,6 @@ function byRefWithoutKeyBreak(array $arr): void
9998
}
10099

101100
/**
102-
* By-ref without key with continue — should still rewrite since all elements are visited.
103101
* @param array<int, string> $arr
104102
*/
105103
function byRefWithoutKeyContinue(array $arr): void
@@ -115,7 +113,6 @@ function byRefWithoutKeyContinue(array $arr): void
115113
}
116114

117115
/**
118-
* By-ref without key with continue where value is overwritten in all branches.
119116
* @param array<int, string> $arr
120117
*/
121118
function byRefWithoutKeyContinueBranches(array $arr): void
@@ -132,7 +129,6 @@ function byRefWithoutKeyContinueBranches(array $arr): void
132129
}
133130

134131
/**
135-
* By-ref without key with continue where value is NOT overwritten in the continue branch.
136132
* @param array<int, string> $arr
137133
*/
138134
function byRefWithoutKeyContinuePartial(array $arr): void
@@ -152,9 +148,6 @@ class Foo
152148
/** @var array<string, int> */
153149
private array $prop;
154150

155-
/**
156-
* By-ref without key on a property.
157-
*/
158151
public function byRefWithoutKeyProperty(): void
159152
{
160153
foreach ($this->prop as &$v) {
@@ -166,7 +159,6 @@ public function byRefWithoutKeyProperty(): void
166159
}
167160

168161
/**
169-
* By-ref without key with intval transformation.
170162
* @param array<int, string> $arr
171163
*/
172164
function byRefWithoutKeyTransform(array $arr): void
@@ -179,15 +171,61 @@ function byRefWithoutKeyTransform(array $arr): void
179171
}
180172

181173
/**
182-
* By-ref without key — value not overwritten at all.
183174
* @param array<int, string> $arr
184175
*/
185176
function byRefWithoutKeyNoOverwrite(array $arr): void
186177
{
187178
foreach ($arr as &$v) {
188-
// just read, don't write
189179
echo $v;
190180
}
191181

192182
assertType('array<int, string>', $arr);
193183
}
184+
185+
/**
186+
* @param list<array{one: string}> $a
187+
*/
188+
function byRefWithKeyModifySubElement(array $a): void
189+
{
190+
foreach ($a as $k => &$testArray) {
191+
$testArray['two'] = 'two';
192+
}
193+
194+
assertType("list<array{one: string, two: 'two'}>", $a);
195+
}
196+
197+
/**
198+
* @param list<array{one: string}> $a
199+
*/
200+
function byRefWithoutKeyModifySubElement(array $a): void
201+
{
202+
foreach ($a as &$testArray) {
203+
$testArray['two'] = 'two';
204+
}
205+
206+
assertType("list<array{one: string, two: 'two'}>", $a);
207+
}
208+
209+
/**
210+
* @param array<int, string> $arr
211+
*/
212+
function byRefWithKeyDirectOverwrite(array $arr): void
213+
{
214+
foreach ($arr as $k => &$v) {
215+
$v = 1;
216+
}
217+
218+
assertType('array<int, 1>', $arr);
219+
}
220+
221+
/**
222+
* @param array<int, string> $arr
223+
*/
224+
function byRefWithoutKeyDirectOverwrite(array $arr): void
225+
{
226+
foreach ($arr as &$v) {
227+
$v = 1;
228+
}
229+
230+
assertType('array<int, 1>', $arr);
231+
}

0 commit comments

Comments
 (0)