Skip to content

Commit f26be0f

Browse files
authored
Speedup analysis of arrays containing closures
1 parent 2109303 commit f26be0f

File tree

4 files changed

+379
-1
lines changed

4 files changed

+379
-1
lines changed

src/Type/Constant/ConstantArrayTypeBuilder.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use PHPStan\Type\Accessory\NonEmptyArrayType;
99
use PHPStan\Type\Accessory\OversizedArrayType;
1010
use PHPStan\Type\ArrayType;
11+
use PHPStan\Type\CallableType;
12+
use PHPStan\Type\ClosureType;
1113
use PHPStan\Type\IntersectionType;
1214
use PHPStan\Type\Type;
1315
use PHPStan\Type\TypeCombinator;
@@ -29,9 +31,12 @@ final class ConstantArrayTypeBuilder
2931
{
3032

3133
public const ARRAY_COUNT_LIMIT = 256;
34+
private const CLOSURES_COUNT_LIMIT = 16;
3235

3336
private bool $degradeToGeneralArray = false;
3437

38+
private bool $degradeClosures = false;
39+
3540
private bool $oversized = false;
3641

3742
/**
@@ -79,6 +84,23 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt
7984
}
8085

8186
if (!$this->degradeToGeneralArray) {
87+
if ($valueType instanceof ClosureType) {
88+
$numClosures = 1;
89+
foreach ($this->valueTypes as $innerType) {
90+
if (!($innerType instanceof ClosureType)) {
91+
continue;
92+
}
93+
94+
$numClosures++;
95+
}
96+
97+
if ($numClosures >= self::CLOSURES_COUNT_LIMIT) {
98+
$this->degradeClosures = true;
99+
$this->degradeToGeneralArray = true;
100+
$this->oversized = true;
101+
}
102+
}
103+
82104
if ($offsetType === null) {
83105
$newAutoIndexes = $optional ? $this->nextAutoIndexes : [];
84106
$hasOptional = false;
@@ -291,9 +313,22 @@ public function getArray(): Type
291313
return new ConstantArrayType($keyTypes, $this->valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
292314
}
293315

316+
if ($this->degradeClosures) {
317+
$itemTypes = [];
318+
$itemTypes[] = new CallableType();
319+
foreach ($this->valueTypes as $valueType) {
320+
if ($valueType instanceof ClosureType) {
321+
continue;
322+
}
323+
$itemTypes[] = $valueType;
324+
}
325+
} else {
326+
$itemTypes = $this->valueTypes;
327+
}
328+
294329
$array = new ArrayType(
295330
TypeCombinator::union(...$this->keyTypes),
296-
TypeCombinator::union(...$this->valueTypes),
331+
TypeCombinator::union(...$itemTypes),
297332
);
298333

299334
$types = [];

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,12 @@ public function testBug13714(): void
15701570
$this->assertSame('Function Bug13714\array_find invoked with 2 parameters, 0 required.', $errors[6]->getMessage());
15711571
}
15721572

1573+
public function testBug13933(): void
1574+
{
1575+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-13933.php');
1576+
$this->assertNoErrors($errors);
1577+
}
1578+
15731579
/**
15741580
* @param string[]|null $allAnalysedFiles
15751581
* @return list<Error>
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
<?php
2+
3+
namespace Bug13933;
4+
5+
$list = [];
6+
7+
$list['a-1'] = static function (): A { return new A(); };
8+
$list['a-2'] = static function (): B { return new B(); };
9+
$list['a-3'] = static function (): C { return new C(); };
10+
$list['a-4'] = static function (): D { return new D(); };
11+
$list['a-5'] = static function (): E { return new E(); };
12+
$list['a-6'] = static function (): F { return new F(); };
13+
$list['string'] = 'hello';
14+
// Beta
15+
$list['b-1'] = static function (): A1 { return new A1(); };
16+
$list['b-2'] = static function (): B1 { return new B1(); };
17+
$list['b-3'] = static function (): C1 { return new C1(); };
18+
$list['b-4'] = static function (): D1 { return new D1(); };
19+
$list['b-5'] = static function (): E1 { return new E1(); };
20+
$list['b-6'] = static function (): F1 { return new F1(); };
21+
$list['int'] = 123;
22+
// Delta
23+
$list['c-1'] = static function (): A2 { return new A2(); };
24+
$list['c-2'] = static function (): B2 { return new B2(); };
25+
$list['c-3'] = static function (): C2 { return new C2(); };
26+
$list['c-4'] = static function (): D2 { return new D2(); };
27+
$list['c-5'] = static function (): E2 { return new E2(); };
28+
$list['c-6'] = static function (): F2 { return new F2(); };
29+
// Epsilon
30+
$list['d-1'] = static function (): A3 { return new A3(); };
31+
$list['d-2'] = static function (): B3 { return new B3(); };
32+
$list['d-3'] = static function (): C3 { return new C3(); };
33+
$list['d-4'] = static function (): D3 { return new D3(); };
34+
$list['d-5'] = static function (): E3 { return new E3(); };
35+
$list['d-6'] = static function (): F3 { return new F3(); };
36+
// Eta
37+
$list['e-1'] = static function (): A4 { return new A4(); };
38+
$list['e-2'] = static function (): B4 { return new B4(); };
39+
$list['e-3'] = static function (): C4 { return new C4(); };
40+
$list['e-4'] = static function (): D4 { return new D4(); };
41+
$list['e-5'] = static function (): E4 { return new E4(); };
42+
$list['e-6'] = static function (): F4 { return new F4(); };
43+
// Gamma
44+
$list['f-1'] = static function (): A5 { return new A5(); };
45+
$list['f-2'] = static function (): B5 { return new B5(); };
46+
$list['f-3'] = static function (): C5 { return new C5(); };
47+
$list['f-4'] = static function (): D5 { return new D5(); };
48+
$list['f-5'] = static function (): E5 { return new E5(); };
49+
$list['f-6'] = static function (): F5 { return new F5(); };
50+
// Iota
51+
$list['g-1'] = static function (): A6 { return new A6(); };
52+
$list['g-2'] = static function (): B6 { return new B6(); };
53+
$list['g-3'] = static function (): C6 { return new C6(); };
54+
$list['g-4'] = static function (): D6 { return new D6(); };
55+
$list['g-5'] = static function (): E6 { return new E6(); };
56+
$list['g-6'] = static function (): F6 { return new F6(); };
57+
// Kappa
58+
$list['h-1'] = static function (): A7 { return new A7(); };
59+
$list['h-2'] = static function (): B7 { return new B7(); };
60+
$list['h-3'] = static function (): C7 { return new C7(); };
61+
$list['h-4'] = static function (): D7 { return new D7(); };
62+
$list['h-5'] = static function (): E7 { return new E7(); };
63+
$list['h-6'] = static function (): F7 { return new F7(); };
64+
// Lambda
65+
$list['i-1'] = static function (): A8 { return new A8(); };
66+
$list['i-2'] = static function (): B8 { return new B8(); };
67+
$list['i-3'] = static function (): C8 { return new C8(); };
68+
$list['i-4'] = static function (): D8 { return new D8(); };
69+
$list['i-5'] = static function (): E8 { return new E8(); };
70+
$list['i-6'] = static function (): F8 { return new F8(); };
71+
// Theta
72+
$list['j-1'] = static function (): A9 { return new A9(); };
73+
$list['j-2'] = static function (): B9 { return new B9(); };
74+
$list['j-3'] = static function (): C9 { return new C9(); };
75+
$list['j-4'] = static function (): D9 { return new D9(); };
76+
$list['j-5'] = static function (): E9 { return new E9(); };
77+
$list['j-6'] = static function (): F9 { return new F9(); };
78+
// Zeta
79+
$list['k-1'] = static function (): A10 { return new A10(); };
80+
$list['k-2'] = static function (): B10 { return new B10(); };
81+
$list['k-3'] = static function (): C10 { return new C10(); };
82+
$list['k-4'] = static function (): D10 { return new D10(); };
83+
$list['k-5'] = static function (): E10 { return new E10(); };
84+
$list['k-6'] = static function (): F10 { return new F10(); };
85+
//
86+
$list['l-1'] = static function (): A11 { return new A11(); };
87+
$list['l-2'] = static function (): B11 { return new B11(); };
88+
$list['l-3'] = static function (): C11 { return new C11(); };
89+
$list['l-4'] = static function (): D11 { return new D11(); };
90+
$list['l-5'] = static function (): E11 { return new E11(); };
91+
$list['l-6'] = static function (): F11 { return new F11(); };
92+
//
93+
$list['m-1'] = static function (): A12 { return new A12(); };
94+
$list['m-2'] = static function (): B12 { return new B12(); };
95+
$list['m-3'] = static function (): C12 { return new C12(); };
96+
$list['m-4'] = static function (): D12 { return new D12(); };
97+
$list['m-5'] = static function (): E12 { return new E12(); };
98+
$list['m-6'] = static function (): F12 { return new F12(); };
99+
//
100+
$list['n-1'] = static function (): A13 { return new A13(); };
101+
$list['n-2'] = static function (): B13 { return new B13(); };
102+
$list['n-3'] = static function (): C13 { return new C13(); };
103+
$list['n-4'] = static function (): D13 { return new D13(); };
104+
$list['n-5'] = static function (): E13 { return new E13(); };
105+
$list['n-6'] = static function (): F13 { return new F13(); };
106+
//
107+
$list['o-1'] = static function (): A14 { return new A14(); };
108+
$list['o-2'] = static function (): B14 { return new B14(); };
109+
$list['o-3'] = static function (): C14 { return new C14(); };
110+
$list['o-4'] = static function (): D14 { return new D14(); };
111+
$list['o-5'] = static function (): E14 { return new E14(); };
112+
$list['o-6'] = static function (): F14 { return new F14(); };
113+
//
114+
$list['p-1'] = static function (): A15 { return new A15(); };
115+
$list['p-2'] = static function (): B15 { return new B15(); };
116+
$list['p-3'] = static function (): C15 { return new C15(); };
117+
$list['p-4'] = static function (): D15 { return new D15(); };
118+
$list['p-5'] = static function (): E15 { return new E15(); };
119+
$list['p-6'] = static function (): F15 { return new F15(); };
120+
//
121+
$list['q-1'] = static function (): A16 { return new A16(); };
122+
$list['q-2'] = static function (): B16 { return new B16(); };
123+
$list['q-3'] = static function (): C16 { return new C16(); };
124+
$list['q-4'] = static function (): D16 { return new D16(); };
125+
$list['q-5'] = static function (): E16 { return new E16(); };
126+
$list['q-6'] = static function (): F16 { return new F16(); };
127+
//
128+
$list['r-1'] = static function (): A16 { return new A16(); };
129+
$list['r-2'] = static function (): B16 { return new B16(); };
130+
$list['r-3'] = static function (): C16 { return new C16(); };
131+
$list['r-4'] = static function (): D16 { return new D16(); };
132+
$list['r-5'] = static function (): E16 { return new E16(); };
133+
$list['r-6'] = static function (): F16 { return new F16(); };
134+
//
135+
$list['s-1'] = static function (): A16 { return new A16(); };
136+
$list['s-2'] = static function (): B16 { return new B16(); };
137+
$list['s-3'] = static function (): C16 { return new C16(); };
138+
$list['s-4'] = static function (): D16 { return new D16(); };
139+
$list['s-5'] = static function (): E16 { return new E16(); };
140+
$list['s-6'] = static function (): F16 { return new F16(); };
141+
//
142+
$list['t-1'] = static function (): A16 { return new A16(); };
143+
$list['t-2'] = static function (): B16 { return new B16(); };
144+
$list['t-3'] = static function (): C16 { return new C16(); };
145+
$list['t-4'] = static function (): D16 { return new D16(); };
146+
$list['t-5'] = static function (): E16 { return new E16(); };
147+
$list['t-6'] = static function (): F16 { return new F16(); };
148+
//
149+
$list['u-1'] = static function (): A16 { return new A16(); };
150+
$list['u-2'] = static function (): B16 { return new B16(); };
151+
$list['u-3'] = static function (): C16 { return new C16(); };
152+
$list['u-4'] = static function (): D16 { return new D16(); };
153+
$list['u-5'] = static function (): E16 { return new E16(); };
154+
$list['u-6'] = static function (): F16 { return new F16(); };
155+
//
156+
$list['v-1'] = static function (): A16 { return new A16(); };
157+
$list['v-2'] = static function (): B16 { return new B16(); };
158+
$list['v-3'] = static function (): C16 { return new C16(); };
159+
$list['v-4'] = static function (): D16 { return new D16(); };
160+
$list['v-5'] = static function (): E16 { return new E16(); };
161+
$list['v-6'] = static function (): F16 { return new F16(); };
162+
//
163+
$list['w-1'] = static function (): A16 { return new A16(); };
164+
$list['w-2'] = static function (): B16 { return new B16(); };
165+
$list['w-3'] = static function (): C16 { return new C16(); };
166+
$list['w-4'] = static function (): D16 { return new D16(); };
167+
$list['w-5'] = static function (): E16 { return new E16(); };
168+
$list['w-6'] = static function (): F16 { return new F16(); };
169+
//
170+
$list['x-1'] = static function (): A16 { return new A16(); };
171+
$list['x-2'] = static function (): B16 { return new B16(); };
172+
$list['x-3'] = static function (): C16 { return new C16(); };
173+
$list['x-4'] = static function (): D16 { return new D16(); };
174+
$list['x-5'] = static function (): E16 { return new E16(); };
175+
$list['x-6'] = static function (): F16 { return new F16(); };
176+
//
177+
$list['y-1'] = static function (): A16 { return new A16(); };
178+
$list['y-2'] = static function (): B16 { return new B16(); };
179+
$list['y-3'] = static function (): C16 { return new C16(); };
180+
$list['y-4'] = static function (): D16 { return new D16(); };
181+
$list['y-5'] = static function (): E16 { return new E16(); };
182+
$list['y-6'] = static function (): F16 { return new F16(); };
183+
//
184+
$list['z-1'] = static function (): A16 { return new A16(); };
185+
$list['z-2'] = static function (): B16 { return new B16(); };
186+
$list['z-3'] = static function (): C16 { return new C16(); };
187+
$list['z-4'] = static function (): D16 { return new D16(); };
188+
$list['z-5'] = static function (): E16 { return new E16(); };
189+
$list['z-6'] = static function (): F16 { return new F16(); };
190+
191+
192+
print 1;
193+
194+
class A {}
195+
class B {}
196+
class C {}
197+
class D {}
198+
class E {}
199+
class F {}
200+
201+
class A1 {}
202+
class B1 {}
203+
class C1 {}
204+
class D1 {}
205+
class E1 {}
206+
class F1 {}
207+
208+
class A2 {}
209+
class B2 {}
210+
class C2 {}
211+
class D2 {}
212+
class E2 {}
213+
class F2 {}
214+
215+
class A3 {}
216+
class B3 {}
217+
class C3 {}
218+
class D3 {}
219+
class E3 {}
220+
class F3 {}
221+
222+
class A4 {}
223+
class B4 {}
224+
class C4 {}
225+
class D4 {}
226+
class E4 {}
227+
class F4 {}
228+
229+
class A5 {}
230+
class B5 {}
231+
class C5 {}
232+
class D5 {}
233+
class E5 {}
234+
class F5 {}
235+
236+
class A6 {}
237+
class B6 {}
238+
class C6 {}
239+
class D6 {}
240+
class E6 {}
241+
class F6 {}
242+
243+
class A7 {}
244+
class B7 {}
245+
class C7 {}
246+
class D7 {}
247+
class E7 {}
248+
class F7 {}
249+
250+
class A8 {}
251+
class B8 {}
252+
class C8 {}
253+
class D8 {}
254+
class E8 {}
255+
class F8 {}
256+
257+
class A9 {}
258+
class B9 {}
259+
class C9 {}
260+
class D9 {}
261+
class E9 {}
262+
class F9 {}
263+
264+
class A10 {}
265+
class B10 {}
266+
class C10 {}
267+
class D10 {}
268+
class E10 {}
269+
class F10 {}
270+
271+
class A11 {}
272+
class B11 {}
273+
class C11 {}
274+
class D11 {}
275+
class E11 {}
276+
class F11 {}
277+
278+
class A12 {}
279+
class B12 {}
280+
class C12 {}
281+
class D12 {}
282+
class E12 {}
283+
class F12 {}
284+
285+
class A13 {}
286+
class B13 {}
287+
class C13 {}
288+
class D13 {}
289+
class E13 {}
290+
class F13 {}
291+
292+
class A14 {}
293+
class B14 {}
294+
class C14 {}
295+
class D14 {}
296+
class E14 {}
297+
class F14 {}
298+
299+
class A15 {}
300+
class B15 {}
301+
class C15 {}
302+
class D15 {}
303+
class E15 {}
304+
class F15 {}
305+
306+
class A16 {}
307+
class B16 {}
308+
class C16 {}
309+
class D16 {}
310+
class E16 {}
311+
class F16 {}

0 commit comments

Comments
 (0)