Skip to content

Commit 873f1e6

Browse files
authored
Fix phpstan/phpstan#14324: Foreach on constant array with closures reaching 32 entries causes crash (#5246)
1 parent 6bac0de commit 873f1e6

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ public function getAllArrays(): array
241241
}
242242

243243
$builder = ConstantArrayTypeBuilder::createEmpty();
244+
$builder->disableArrayDegradation();
244245
foreach ($keys as $i) {
245246
$builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i]);
246247
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,13 @@ public function testBigPhpdocArrayShape(): void
14921492
$this->assertNoErrors($errors);
14931493
}
14941494

1495+
public function testBug14324(): void
1496+
{
1497+
// crash
1498+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-14324.php');
1499+
$this->assertNoErrors($errors);
1500+
}
1501+
14951502
/**
14961503
* @param string[]|null $allAnalysedFiles
14971504
* @return list<Error>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14324;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
final class Test
8+
{
9+
private const ADDITIONAL_MAPS = [
10+
'foo-',
11+
'bar-',
12+
'baz-',
13+
];
14+
15+
/** @var array<string, callable(): string> */
16+
private static array $map = [];
17+
18+
public function createMap(): void
19+
{
20+
if ([] === self::$map) {
21+
// 29 entries
22+
self::$map = [
23+
'foo' => static fn() => 'foo',
24+
'bar' => static fn() => 'bar',
25+
'baz' => static fn() => 'baz',
26+
'qux' => static fn() => 'qux',
27+
'quux' => static fn() => 'quux',
28+
'corge' => static fn() => 'corge',
29+
'grault' => static fn() => 'grault',
30+
'garply' => static fn() => 'garply',
31+
'waldo' => static fn() => 'waldo',
32+
'fred' => static fn() => 'fred',
33+
'plugh' => static fn() => 'plugh',
34+
'xyzzy' => static fn() => 'xyzzy',
35+
'thud' => static fn() => 'thud',
36+
'foo1' => static fn() => 'foo1',
37+
'bar1' => static fn() => 'bar1',
38+
'baz1' => static fn() => 'baz1',
39+
'qux1' => static fn() => 'qux1',
40+
'quux1' => static fn() => 'quux1',
41+
'corge1' => static fn() => 'corge1',
42+
'grault1' => static fn() => 'grault1',
43+
'garply1' => static fn() => 'garply1',
44+
'waldo1' => static fn() => 'waldo1',
45+
'fred1' => static fn() => 'fred1',
46+
'plugh1' => static fn() => 'plugh1',
47+
'xyzzy1' => static fn() => 'xyzzy1',
48+
'thud1' => static fn() => 'thud1',
49+
'foo2' => static fn() => 'foo2',
50+
'bar2' => static fn() => 'bar2',
51+
'baz2' => static fn() => 'baz2',
52+
];
53+
assertType("array{foo: Closure(): 'foo', bar: Closure(): 'bar', baz: Closure(): 'baz', qux: Closure(): 'qux', quux: Closure(): 'quux', corge: Closure(): 'corge', grault: Closure(): 'grault', garply: Closure(): 'garply', waldo: Closure(): 'waldo', fred: Closure(): 'fred', plugh: Closure(): 'plugh', xyzzy: Closure(): 'xyzzy', thud: Closure(): 'thud', foo1: Closure(): 'foo1', bar1: Closure(): 'bar1', baz1: Closure(): 'baz1', qux1: Closure(): 'qux1', quux1: Closure(): 'quux1', corge1: Closure(): 'corge1', grault1: Closure(): 'grault1', garply1: Closure(): 'garply1', waldo1: Closure(): 'waldo1', fred1: Closure(): 'fred1', plugh1: Closure(): 'plugh1', xyzzy1: Closure(): 'xyzzy1', thud1: Closure(): 'thud1', foo2: Closure(): 'foo2', bar2: Closure(): 'bar2', baz2: Closure(): 'baz2'}", self::$map);
54+
55+
foreach (self::ADDITIONAL_MAPS as $map) {
56+
// added with 3 entries, breaching the closure limit of 32 entries
57+
self::$map[$map] = fn () => self::$map['foo']();
58+
}
59+
60+
assertType("non-empty-array<'bar'|'bar-'|'bar1'|'bar2'|'baz'|'baz-'|'baz1'|'baz2'|'corge'|'corge1'|'foo'|'foo-'|'foo1'|'foo2'|'fred'|'fred1'|'garply'|'garply1'|'grault'|'grault1'|'plugh'|'plugh1'|'quux'|'quux1'|'qux'|'qux1'|'thud'|'thud1'|'waldo'|'waldo1'|'xyzzy'|'xyzzy1', callable(): mixed>&oversized-array", self::$map);
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)