Skip to content

Commit 6586de9

Browse files
committed
Fix for performance issue
Closes phpstan/phpstan#14462
1 parent 04a99c1 commit 6586de9

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed

src/Type/TypeCombinator.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,27 @@ private static function reduceArrays(array $constantArrays, bool $preserveTagged
11541154
continue;
11551155
}
11561156

1157+
// Merge two single-key arrays sharing the same key when their value
1158+
// types union into a single type (not a UnionType). This is lossless
1159+
// and prevents exponential union growth when narrowing nested
1160+
// ArrayDimFetch expressions on a ConstantArrayType parent (see
1161+
// phpstan/phpstan#14462).
1162+
if (
1163+
$preserveTaggedUnions
1164+
&& $overlappingKeysCount === 1
1165+
&& count($arraysToProcess[$i]->getKeyTypes()) === 1
1166+
&& count($arraysToProcess[$j]->getKeyTypes()) === 1
1167+
) {
1168+
$iValueType = $arraysToProcess[$i]->getValueTypes()[0];
1169+
$jValueType = $arraysToProcess[$j]->getValueTypes()[0];
1170+
$unionValueType = self::union($iValueType, $jValueType);
1171+
if (!$unionValueType instanceof UnionType) {
1172+
$arraysToProcess[$j] = $arraysToProcess[$j]->mergeWith($arraysToProcess[$i]);
1173+
unset($arraysToProcess[$i]);
1174+
continue 2;
1175+
}
1176+
}
1177+
11571178
if (
11581179
$preserveTaggedUnions
11591180
&& $overlappingKeysCount === count($arraysToProcess[$i]->getKeyTypes())

tests/PHPStan/Analyser/data/bug-8004.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public function getErrorsOnInvalidQuestions(array $importQuiz, int $key): array
7373
}
7474
}
7575

76-
assertType("list<array{line: int, type: 'empty_answer'|'empty_question'|'invalid_answer_1_too_long'|'invalid_answer_1_type'|'invalid_answer_2_too_long'|'invalid_answer_2_type'|'invalid_answer_3_too_long'|'invalid_answer_3_type'|'invalid_answer_4_too_long'|'invalid_answer_4_type'|'invalid_question_too_long'|'invalid_right_answer', value: int}&oversized-array>&oversized-array", $errors);
76+
assertType("list<array{line: int, type: 'empty_answer'|'empty_question'|'invalid_answer_1_too_long'|'invalid_answer_1_type'|'invalid_answer_2_too_long'|'invalid_answer_2_type'|'invalid_answer_3_too_long'|'invalid_answer_3_type'|'invalid_answer_4_too_long'|'invalid_answer_4_type'|'invalid_question_too_long'|'invalid_right_answer', value: int}>", $errors);
7777

7878
return $errors;
7979
}

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public function testRule(): void
106106
145,
107107
],
108108
[
109-
'Offset \'c\' might not exist on array{c: false}|array{c: true}|array{e: true}.',
109+
'Offset \'c\' might not exist on array{c: bool}|array{e: true}.',
110110
171,
111111
],
112112
[

tests/bench/data/bug-14462.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14462;
4+
5+
/** @return array{menu: array<non-empty-string, bool>} */
6+
function get_config(): array {
7+
return ['menu' => []];
8+
}
9+
10+
$config = get_config();
11+
12+
$data = [ ];
13+
if ($config['menu']['notefrais']) {
14+
$data[] = [ 'name' => 'notefrais', 'menu' => 'notefrais_base' ];
15+
}
16+
if ($config['menu']['achat']) {
17+
$data[] = [ 'name' => 'achat', 'menu' => 'achat_base' ];
18+
}
19+
20+
if ($config['menu']['vente-commande_planning'] || $config['menu']['vente-commande']) {
21+
$data[] = [ 'name' => 'vente' , 'menu' => 'vente_order_recent' ];
22+
}
23+
if ($config['menu']['vente-commande_planning']) {
24+
$data[] = [ 'name' => 'vente', 'menu' => 'vente_base_planned' ];
25+
}
26+
if ($config['menu']['vente-commande']) {
27+
$data[] = [ 'name' => 'vente', 'menu' => 'vente_base_com' ];
28+
}
29+
if ($config['menu']['carte']) {
30+
$data[] = [ 'name' => 'carte', 'menu' => '' ];
31+
}
32+
if ($config['menu']['crm']) {
33+
$data[] = [ 'name' => 'crm', 'menu' => 'crm_suivi' ];
34+
}
35+
if ($config['menu']['inventaire']) {
36+
$data[] = [ 'name' => 'inventaire', 'menu' => 'inventaire_base' ];
37+
}
38+
39+
40+
foreach ($data as $row) {
41+
$stack = [ ];
42+
if ($row['menu'] === 'vente_order_recent') {
43+
$stack[] = 'f';
44+
}
45+
else {
46+
$stack[] = 'g';
47+
}
48+
}

0 commit comments

Comments
 (0)