@@ -193,7 +193,7 @@ class NodeScopeResolver
193193 private const LOOP_SCOPE_ITERATIONS = 3 ;
194194 private const GENERALIZE_AFTER_ITERATION = 1 ;
195195 private const FOREACH_UNROLL_LIMIT = 16 ;
196- private const FOREACH_UNROLL_NESTED_LIMIT = 16 ;
196+ private const FOREACH_UNROLL_NESTED_LIMIT = 8 ;
197197
198198 /** @var array<string, true> filePath(string) => bool(true) */
199199 private array $ analysedFiles = [];
@@ -1450,6 +1450,7 @@ public function processStmtNode(
14501450
14511451 $ originalStorage = $ storage ;
14521452 $ unrolledEndScope = null ;
1453+ $ unrolledTotalKeys = null ;
14531454 if ($ context ->isTopLevel ()) {
14541455 $ storage = $ originalStorage ->duplicate ();
14551456
@@ -1458,6 +1459,7 @@ public function processStmtNode(
14581459 if ($ unrolledResult !== null ) {
14591460 $ bodyScope = $ unrolledResult ['bodyScope ' ];
14601461 $ unrolledEndScope = $ unrolledResult ['endScope ' ];
1462+ $ unrolledTotalKeys = $ unrolledResult ['totalKeys ' ];
14611463 } else {
14621464 $ bodyScope = $ this ->enterForeach ($ originalScope , $ storage , $ originalScope , $ stmt , $ nodeCallback );
14631465 $ count = 0 ;
@@ -1486,7 +1488,8 @@ public function processStmtNode(
14861488 $ bodyScope = $ bodyScope ->mergeWith ($ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope );
14871489 $ storage = $ originalStorage ;
14881490 $ bodyScope = $ this ->enterForeach ($ bodyScope , $ storage , $ originalScope , $ stmt , $ nodeCallback );
1489- $ finalScopeResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
1491+ $ finalPassContext = $ unrolledTotalKeys !== null ? $ context ->enterUnrolledForeach ($ unrolledTotalKeys ) : $ context ;
1492+ $ finalScopeResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ finalPassContext )->filterOutLoopExitPoints ();
14901493 $ finalScope = $ finalScopeResult ->getScope ();
14911494 $ scopesWithIterableValueType = [];
14921495
@@ -4107,7 +4110,7 @@ public function processVarAnnotation(MutatingScope $scope, array $variableNames,
41074110 }
41084111
41094112 /**
4110- * @return array{bodyScope: MutatingScope, endScope: MutatingScope}|null
4113+ * @return array{bodyScope: MutatingScope, endScope: MutatingScope, totalKeys: int }|null
41114114 */
41124115 private function tryProcessUnrolledConstantArrayForeach (
41134116 Foreach_ $ stmt ,
@@ -4142,7 +4145,8 @@ private function tryProcessUnrolledConstantArrayForeach(
41424145 if ($ totalKeys === 0 || $ totalKeys > self ::FOREACH_UNROLL_LIMIT ) {
41434146 return null ;
41444147 }
4145- if ($ context ->getForeachUnrollFactor () * $ totalKeys > self ::FOREACH_UNROLL_NESTED_LIMIT ) {
4148+ $ foreachUnrollFactor = $ context ->getForeachUnrollFactor ();
4149+ if ($ foreachUnrollFactor > 1 && $ foreachUnrollFactor * $ totalKeys > self ::FOREACH_UNROLL_NESTED_LIMIT ) {
41464150 return null ;
41474151 }
41484152
@@ -4270,7 +4274,7 @@ private function tryProcessUnrolledConstantArrayForeach(
42704274 $ endScope = $ endScope ->mergeWith ($ breakScope );
42714275 }
42724276
4273- return ['bodyScope ' => $ bodyScope , 'endScope ' => $ endScope ];
4277+ return ['bodyScope ' => $ bodyScope , 'endScope ' => $ endScope, ' totalKeys ' => $ totalKeys ];
42744278 }
42754279
42764280 private function getTraversableForeachThrowPoint (MutatingScope $ scope , Expr $ iteratee ): ?InternalThrowPoint
0 commit comments