Skip to content

Commit d7d28b9

Browse files
Rework
1 parent 86e7f3f commit d7d28b9

File tree

2 files changed

+124
-50
lines changed

2 files changed

+124
-50
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -742,8 +742,31 @@ public function unsetOffset(Type $offsetType): Type
742742
}
743743

744744
$newIsList = TrinaryLogic::createNo();
745-
if (!$this->isList->no() && in_array($i, $this->optionalKeys, true)) {
746-
$newIsList = TrinaryLogic::createMaybe();
745+
if (!$this->isList->no()) {
746+
$preserveIsList = true;
747+
$isListOnlyIfKeysAreOptional = false;
748+
foreach ($newKeyTypes as $k2 => $newKeyType2) {
749+
if (!$newKeyType2 instanceof ConstantIntegerType || $newKeyType2->getValue() !== $k2) {
750+
// We found a non-optional key that implies that the array is never a list.
751+
if (!in_array($k2, $newOptionalKeys, true)) {
752+
$preserveIsList = false;
753+
break;
754+
}
755+
756+
// The array can still be a list if all the following keys are also optional.
757+
$isListOnlyIfKeysAreOptional = true;
758+
continue;
759+
}
760+
761+
if ($isListOnlyIfKeysAreOptional && !in_array($k2, $newOptionalKeys, true)) {
762+
$preserveIsList = false;
763+
break;
764+
}
765+
}
766+
767+
if ($preserveIsList) {
768+
$newIsList = $this->isList;
769+
}
747770
}
748771

749772
return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, $newIsList);

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

Lines changed: 99 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,54 +19,6 @@ public function testList(array $b): void
1919
assertType('list{0: string, 1: string, 2?: string, 3?: string}', $b);
2020
}
2121

22-
/**
23-
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
24-
*/
25-
public function testUnset0(array $b): void
26-
{
27-
assertType('true', array_is_list($b));
28-
unset($b[0]);
29-
assertType('false', array_is_list($b));
30-
$b[] = 'foo';
31-
assertType('false', array_is_list($b));
32-
}
33-
34-
/**
35-
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
36-
*/
37-
public function testUnset1(array $b): void
38-
{
39-
assertType('true', array_is_list($b));
40-
unset($b[1]);
41-
assertType('bool', array_is_list($b));
42-
$b[] = 'foo';
43-
assertType('false', array_is_list($b));
44-
}
45-
46-
/**
47-
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
48-
*/
49-
public function testUnset2(array $b): void
50-
{
51-
assertType('true', array_is_list($b));
52-
unset($b[2]);
53-
assertType('bool', array_is_list($b));
54-
$b[] = 'foo';
55-
assertType('false', array_is_list($b));
56-
}
57-
58-
/**
59-
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
60-
*/
61-
public function testUnset3(array $b): void
62-
{
63-
assertType('true', array_is_list($b));
64-
unset($b[3]);
65-
assertType('true', array_is_list($b));
66-
$b[] = 'foo';
67-
assertType('false', array_is_list($b));
68-
}
69-
7022
public function placeholderToEditor(string $html): void
7123
{
7224
$result = preg_replace_callback(
@@ -117,3 +69,102 @@ function (array $matches): string {
11769
);
11870
}
11971
}
72+
73+
class HelloWorld2
74+
{
75+
/**
76+
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
77+
*/
78+
public function testUnset0OnList(array $b): void
79+
{
80+
assertType('true', array_is_list($b));
81+
unset($b[0]);
82+
assertType('false', array_is_list($b));
83+
$b[] = 'foo';
84+
assertType('false', array_is_list($b));
85+
}
86+
87+
/**
88+
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
89+
*/
90+
public function testUnset1OnList(array $b): void
91+
{
92+
assertType('true', array_is_list($b));
93+
unset($b[1]);
94+
assertType('bool', array_is_list($b));
95+
$b[] = 'foo';
96+
assertType('bool', array_is_list($b)); // Could be false
97+
}
98+
99+
/**
100+
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
101+
*/
102+
public function testUnset2OnList(array $b): void
103+
{
104+
assertType('true', array_is_list($b));
105+
unset($b[2]);
106+
assertType('bool', array_is_list($b));
107+
$b[] = 'foo';
108+
assertType('bool', array_is_list($b)); // Could be false
109+
}
110+
111+
/**
112+
* @param list{0: string, 1: string, 2?: string, 3?: string} $b
113+
*/
114+
public function testUnset3OnList(array $b): void
115+
{
116+
assertType('true', array_is_list($b));
117+
unset($b[3]);
118+
assertType('bool', array_is_list($b)); // Could be true
119+
$b[] = 'foo';
120+
assertType('bool', array_is_list($b)); // Could be false
121+
}
122+
123+
/**
124+
* @param array{0: string, 1?: string, 2: string, 3?: string} $b
125+
*/
126+
public function testUnset0OnArray(array $b): void
127+
{
128+
assertType('bool', array_is_list($b));
129+
unset($b[0]);
130+
assertType('false', array_is_list($b));
131+
$b[] = 'foo';
132+
assertType('false', array_is_list($b));
133+
}
134+
135+
/**
136+
* @param array{0: string, 1?: string, 2: string, 3?: string} $b
137+
*/
138+
public function testUnset1OnArray(array $b): void
139+
{
140+
assertType('bool', array_is_list($b));
141+
unset($b[1]);
142+
assertType('false', array_is_list($b));
143+
$b[] = 'foo';
144+
assertType('false', array_is_list($b));
145+
}
146+
147+
/**
148+
* @param array{0: string, 1?: string, 2: string, 3?: string} $b
149+
*/
150+
public function testUnset2OnArray(array $b): void
151+
{
152+
assertType('bool', array_is_list($b));
153+
unset($b[2]);
154+
assertType('bool', array_is_list($b));
155+
$b[] = 'foo';
156+
assertType('bool', array_is_list($b)); // Could be false
157+
}
158+
159+
/**
160+
* @param array{0: string, 1?: string, 2: string, 3?: string} $b
161+
*/
162+
public function testUnset3OnArray(array $b): void
163+
{
164+
assertType('bool', array_is_list($b));
165+
unset($b[3]);
166+
assertType('bool', array_is_list($b));
167+
$b[] = 'foo';
168+
assertType('bool', array_is_list($b)); // Could be false
169+
}
170+
}

0 commit comments

Comments
 (0)