Skip to content

Commit

Permalink
Optimize array_map with many arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 13, 2024
1 parent 44e40f0 commit 09fbc92
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 16 deletions.
38 changes: 23 additions & 15 deletions src/Type/Php/ArrayMapFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,31 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
$constantArrays = $arrayType->getConstantArrays();
if (count($constantArrays) > 0) {
$arrayTypes = [];
foreach ($constantArrays as $constantArray) {
$returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty();
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
$returnedArrayBuilder->setOffsetValueType(
$keyType,
$valueType,
$constantArray->isOptionalKey($i),
);
$totalCount = TypeCombinator::countConstantArrayValueTypes($constantArrays) * TypeCombinator::countConstantArrayValueTypes([$valueType]);
if ($totalCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
foreach ($constantArrays as $constantArray) {
$returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty();
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
$returnedArrayBuilder->setOffsetValueType(
$keyType,
$valueType,
$constantArray->isOptionalKey($i),
);
}
$returnedArray = $returnedArrayBuilder->getArray();
if ($constantArray->isList()->yes()) {
$returnedArray = AccessoryArrayListType::intersectWith($returnedArray);
}
$arrayTypes[] = $returnedArray;
}
$returnedArray = $returnedArrayBuilder->getArray();
if ($constantArray->isList()->yes()) {
$returnedArray = AccessoryArrayListType::intersectWith($returnedArray);
}
$arrayTypes[] = $returnedArray;
}

$mappedArrayType = TypeCombinator::union(...$arrayTypes);
$mappedArrayType = TypeCombinator::union(...$arrayTypes);
} else {
$mappedArrayType = TypeCombinator::intersect(new ArrayType(
$arrayType->getIterableKeyType(),
$valueType,
), ...TypeUtils::getAccessoryTypes($arrayType));
}
} elseif ($arrayType->isArray()->yes()) {
$mappedArrayType = TypeCombinator::intersect(new ArrayType(
$arrayType->getIterableKeyType(),
Expand Down
2 changes: 1 addition & 1 deletion src/Type/TypeCombinator.php
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ private static function optimizeConstantArrays(array $types): array
/**
* @param Type[] $types
*/
private static function countConstantArrayValueTypes(array $types): int
public static function countConstantArrayValueTypes(array $types): int
{
$constantArrayValuesCount = 0;
foreach ($types as $type) {
Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,16 @@ public function testBug11292(): void
$this->assertNoErrors($errors);
}

public function testBug11297(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$errors = $this->runAnalyse(__DIR__ . '/data/bug-11297.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
251 changes: 251 additions & 0 deletions tests/PHPStan/Analyser/data/bug-11297.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<?php // lint >= 8.1

namespace Bug11297;

class ClassA {
/** @param array<mixed> $array */
public static function doSomething(string $string, array $array): void
{
}
}

enum Icon: string
{
case CASE1 = 'case1';
case CASE2 = 'case2';
case CASE3 = 'case3';
case CASE4 = 'case4';
case CASE5 = 'case5';
case CASE6 = 'case6';
case CASE7 = 'case7';
case CASE8 = 'case8';
case CASE9 = 'case9';
case CASE10 = 'case10';
case CASE11 = 'case11';
case CASE12 = 'case12';
case CASE13 = 'case13';
case CASE14 = 'case14';
case CASE15 = 'case15';
case CASE16 = 'case16';
case CASE17 = 'case17';
case CASE18 = 'case18';
case CASE19 = 'case19';
case CASE20 = 'case20';
case CASE21 = 'case21';
case CASE22 = 'case22';
case CASE23 = 'case23';
case CASE24 = 'case24';
case CASE25 = 'case25';
case CASE26 = 'case26';
case CASE27 = 'case27';
case CASE28 = 'case28';
case CASE29 = 'case29';
case CASE30 = 'case30';
case CASE31 = 'case31';
case CASE32 = 'case32';
case CASE33 = 'case33';
case CASE34 = 'case34';
case CASE35 = 'case35';
case CASE36 = 'case36';
case CASE37 = 'case37';
case CASE38 = 'case38';
case CASE39 = 'case39';
case CASE40 = 'case40';
case CASE41 = 'case41';
case CASE42 = 'case42';
case CASE43 = 'case43';
case CASE44 = 'case44';
case CASE45 = 'case45';
case CASE46 = 'case46';
case CASE47 = 'case47';
case CASE48 = 'case48';
case CASE49 = 'case49';
case CASE50 = 'case50';
case CASE51 = 'case51';
case CASE52 = 'case52';
case CASE53 = 'case53';
case CASE54 = 'case54';
case CASE55 = 'case55';
case CASE56 = 'case56';
case CASE57 = 'case57';
case CASE58 = 'case58';
case CASE59 = 'case59';
case CASE60 = 'case60';
case CASE61 = 'case61';
case CASE62 = 'case62';
case CASE63 = 'case63';
case CASE64 = 'case64';
case CASE65 = 'case65';
case CASE66 = 'case66';
case CASE67 = 'case67';
case CASE68 = 'case68';
case CASE69 = 'case69';
case CASE70 = 'case70';
case CASE71 = 'case71';
case CASE72 = 'case72';
case CASE73 = 'case73';
case CASE74 = 'case74';
case CASE75 = 'case75';
case CASE76 = 'case76';
case CASE77 = 'case77';
case CASE78 = 'case78';
case CASE79 = 'case79';
case CASE80 = 'case80';
case CASE81 = 'case81';
case CASE82 = 'case82';
case CASE83 = 'case83';
case CASE84 = 'case84';
case CASE85 = 'case85';
case CASE86 = 'case86';
case CASE87 = 'case87';
case CASE88 = 'case88';
case CASE89 = 'case89';
case CASE90 = 'case90';
case CASE91 = 'case91';
case CASE92 = 'case92';
case CASE93 = 'case93';
case CASE94 = 'case94';
case CASE95 = 'case95';
case CASE96 = 'case96';
case CASE97 = 'case97';
case CASE98 = 'case98';
case CASE99 = 'case99';
case CASE100 = 'case100';
case CASE101 = 'case101';
case CASE102 = 'case102';
case CASE103 = 'case103';
case CASE104 = 'case104';
case CASE105 = 'case105';
case CASE106 = 'case106';
case CASE107 = 'case107';
case CASE108 = 'case108';
case CASE109 = 'case109';
case CASE110 = 'case110';
case CASE111 = 'case111';
case CASE112 = 'case112';
case CASE113 = 'case113';
case CASE114 = 'case114';
case CASE115 = 'case115';
case CASE116 = 'case116';
case CASE117 = 'case117';
case CASE118 = 'case118';
case CASE119 = 'case119';
case CASE120 = 'case120';
case CASE121 = 'case121';
case CASE122 = 'case122';
case CASE123 = 'case123';
case CASE124 = 'case124';
case CASE125 = 'case125';
case CASE126 = 'case126';
case CASE127 = 'case127';
case CASE128 = 'case128';
case CASE129 = 'case129';
case CASE130 = 'case130';
case CASE131 = 'case131';
case CASE132 = 'case132';
case CASE133 = 'case133';
case CASE134 = 'case134';
case CASE135 = 'case135';
case CASE136 = 'case136';
case CASE137 = 'case137';
case CASE138 = 'case138';
case CASE139 = 'case139';
case CASE140 = 'case140';
case CASE141 = 'case141';
case CASE142 = 'case142';
case CASE143 = 'case143';
case CASE144 = 'case144';
case CASE145 = 'case145';
case CASE146 = 'case146';
case CASE147 = 'case147';
case CASE148 = 'case148';
case CASE149 = 'case149';
case CASE150 = 'case150';
case CASE151 = 'case151';
case CASE152 = 'case152';
case CASE153 = 'case153';
case CASE154 = 'case154';
case CASE155 = 'case155';
case CASE156 = 'case156';
case CASE157 = 'case157';
case CASE158 = 'case158';
case CASE159 = 'case159';
case CASE160 = 'case160';
case CASE161 = 'case161';
case CASE162 = 'case162';
case CASE163 = 'case163';
case CASE164 = 'case164';
case CASE165 = 'case165';
case CASE166 = 'case166';
case CASE167 = 'case167';
case CASE168 = 'case168';
case CASE169 = 'case169';
case CASE170 = 'case170';
case CASE171 = 'case171';
case CASE172 = 'case172';
case CASE173 = 'case173';
case CASE174 = 'case174';
case CASE175 = 'case175';
case CASE176 = 'case176';
case CASE177 = 'case177';
case CASE178 = 'case178';
case CASE179 = 'case179';
case CASE180 = 'case180';
case CASE181 = 'case181';
case CASE182 = 'case182';
case CASE183 = 'case183';
case CASE184 = 'case184';
case CASE185 = 'case185';
case CASE186 = 'case186';
case CASE187 = 'case187';
case CASE188 = 'case188';
case CASE189 = 'case189';
case CASE190 = 'case190';
case CASE191 = 'case191';
case CASE192 = 'case192';
case CASE193 = 'case193';
case CASE194 = 'case194';
case CASE195 = 'case195';
case CASE196 = 'case196';
case CASE197 = 'case197';
case CASE198 = 'case198';
case CASE199 = 'case199';
case CASE200 = 'case200';

public function getFileIdentifier(): string
{
return match ($this) {
default => $this->value
};
}

public function getBackendLabelIdentifier(): string
{
return 'foo:' . $this->getLocallangIdentifier();
}

private function getLocallangIdentifier(): string
{
return 'foo.icon.' . $this->value;
}
}

(static function (string $table): void {
/**
* Add TCA for top bar field in pages.
*/
ClassA::doSomething($table, [
'foo' => [
'config' => [
'items' => [['', ''], ...array_map(
static fn (Icon $icon): array => [
$icon->getBackendLabelIdentifier(),
$icon->value,
'foo/' . $icon->getFileIdentifier() . '.svg',
],
Icon::cases(),
)],
],
],
]);
})('foo');

0 comments on commit 09fbc92

Please sign in to comment.