@@ -232,10 +232,12 @@ static function () use ($condResult, $emptyArrayType): Type {
232232 try {
233233 $ bodyScope = $ this ->enterForeach ($ nodeScopeResolver , $ bodyScope , $ storage , $ originalScope , $ stmt , $ foreachIterateeType , $ foreachNativeIterateeType , $ nodeCallback );
234234 $ bodyScopeResult = $ nodeScopeResolver ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ bodyRecording , $ context ->enterDeep ()->withoutTemplateArgumentResolution ())->filterOutLoopExitPoints ();
235- $ bodyScope = $ bodyScopeResult ->getScope ();
236- foreach ($ bodyScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
237- $ bodyScope = $ bodyScope ->mergeWith ($ continueExitPoint ->getScope ());
235+ $ backEdgeScope = $ bodyScopeResult ->getLoopBackEdgeScope ();
236+ if ($ backEdgeScope === null ) {
237+ $ bodyScope = $ prevScope ;
238+ break ;
238239 }
240+ $ bodyScope = $ backEdgeScope ;
239241 } finally {
240242 $ scope ->popExpressionResultStorage ();
241243 }
@@ -278,7 +280,7 @@ static function () use ($condResult, $emptyArrayType): Type {
278280 $ finalPassContext = $ unrolledTotalKeys !== null ? $ context ->enterUnrolledForeach ($ unrolledTotalKeys ) : $ context ;
279281 $ finalScopeResult = $ nodeScopeResolver ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ finalPassContext )->filterOutLoopExitPoints ();
280282 }
281- $ finalScope = $ finalScopeResult ->getScope ();
283+ $ finalScope = $ finalScopeResult ->isEndReachable () ? $ finalScopeResult -> getScope () : null ;
282284 $ scopesWithIterableValueType = [];
283285
284286 $ keyVarExpr = null ;
@@ -299,7 +301,7 @@ static function () use ($condResult, $emptyArrayType): Type {
299301 $ trackingExpr = $ originalKeyVarExpr ?? $ originalValueExpr ;
300302
301303 $ continueExitPointHasUnoriginalKeyType = false ;
302- if ($ trackingExpr !== null ) {
304+ if ($ trackingExpr !== null && $ finalScope !== null ) {
303305 if ($ finalScope ->hasExpressionType ($ trackingExpr )->yes ()) {
304306 $ scopesWithIterableValueType [] = $ finalScope ;
305307 } else {
@@ -309,7 +311,7 @@ static function () use ($condResult, $emptyArrayType): Type {
309311
310312 foreach ($ finalScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
311313 $ continueScope = $ continueExitPoint ->getScope ();
312- $ finalScope = $ continueScope ->mergeWith ($ finalScope );
314+ $ finalScope = $ finalScope === null ? $ continueScope : $ continueScope ->mergeWith ($ finalScope );
313315 if ($ trackingExpr === null || !$ continueScope ->hasExpressionType ($ trackingExpr )->yes ()) {
314316 $ continueExitPointHasUnoriginalKeyType = true ;
315317 continue ;
@@ -318,8 +320,9 @@ static function () use ($condResult, $emptyArrayType): Type {
318320 }
319321 $ breakExitPoints = $ finalScopeResult ->getExitPointsByType (Break_::class);
320322 foreach ($ breakExitPoints as $ breakExitPoint ) {
321- $ finalScope = $ breakExitPoint ->getScope ()->mergeWith ($ finalScope );
323+ $ finalScope = $ finalScope === null ? $ breakExitPoint -> getScope () : $ breakExitPoint ->getScope ()->mergeWith ($ finalScope );
322324 }
325+ $ finalScope ??= $ finalScopeResult ->getScope ();
323326
324327 if ($ unrolledEndScope !== null ) {
325328 $ finalScope = $ unrolledEndScope ;
@@ -628,7 +631,7 @@ private function enterForeach(NodeScopeResolver $nodeScopeResolver, MutatingScop
628631 }
629632
630633 /**
631- * @return array{bodyScope: MutatingScope, endScope: MutatingScope, totalKeys: int}|null
634+ * @return array{bodyScope: MutatingScope, endScope: MutatingScope|null , totalKeys: int}|null
632635 */
633636 private function tryProcessUnrolledConstantArrayForeach (
634637 NodeScopeResolver $ nodeScopeResolver ,
@@ -755,14 +758,22 @@ private function tryProcessUnrolledConstantArrayForeach(
755758 $ bodyContext ,
756759 )->filterOutLoopExitPoints ();
757760
758- $ iterEndScope = $ bodyResult ->getScope ();
759- foreach ($ bodyResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
760- $ iterEndScope = $ iterEndScope ->mergeWith ($ continueExitPoint ->getScope ());
761- }
761+ $ iterEndScope = $ bodyResult ->getLoopBackEdgeScope ();
762762 foreach ($ bodyResult ->getExitPointsByType (Break_::class) as $ breakExitPoint ) {
763763 $ allBreakScopes [] = $ breakExitPoint ->getScope ();
764764 }
765765
766+ if ($ iterEndScope === null ) {
767+ if ($ isOptional ) {
768+ // the key may be missing, the next iteration then starts from the previous one
769+ continue ;
770+ }
771+
772+ // no later iteration runs, the loop is left only through its break statements
773+ $ chainScope = null ;
774+ break ;
775+ }
776+
766777 if ($ isOptional ) {
767778 $ chainScope = $ iterEndScope ->mergeWith ($ chainScope );
768779 } else {
@@ -774,11 +785,15 @@ private function tryProcessUnrolledConstantArrayForeach(
774785 for ($ i = 1 , $ c = count ($ entryScopes ); $ i < $ c ; $ i ++) {
775786 $ arrayBodyScope = $ arrayBodyScope ->mergeWith ($ entryScopes [$ i ]);
776787 }
777- if (count ($ entryScopes ) === 1 ) {
788+ if (count ($ entryScopes ) === 1 && $ chainScope !== null ) {
778789 $ arrayBodyScope = $ arrayBodyScope ->mergeWith ($ chainScope );
779790 }
780791
781792 $ allBodyScopes [] = $ arrayBodyScope ;
793+ if ($ chainScope === null ) {
794+ continue ;
795+ }
796+
782797 $ allChainScopes [] = $ chainScope ;
783798 }
784799
@@ -791,21 +806,22 @@ private function tryProcessUnrolledConstantArrayForeach(
791806 $ bodyScope = $ bodyScope ->mergeWith ($ allBodyScopes [$ i ]);
792807 }
793808
794- $ endScope = $ allChainScopes [ 0 ] ;
795- for ($ i = 1 , $ c = count ( $ allChainScopes); $ i < $ c ; $ i ++ ) {
796- $ endScope = $ endScope ->mergeWith ($ allChainScopes [ $ i ] );
809+ $ chainEndScope = null ;
810+ foreach ($ allChainScopes as $ chainScope ) {
811+ $ chainEndScope = $ chainEndScope === null ? $ chainScope : $ chainEndScope ->mergeWith ($ chainScope );
797812 }
798813
814+ $ endScope = $ chainEndScope ;
799815 foreach ($ allBreakScopes as $ breakScope ) {
800- $ endScope = $ endScope ->mergeWith ($ breakScope );
816+ $ endScope = $ endScope === null ? $ breakScope : $ endScope ->mergeWith ($ breakScope );
801817 }
802818
803819 // Unsealed shapes describe zero-or-more additional entries beyond the
804820 // explicit keys. Run the scope-generalizing loop on top of the
805821 // unrolled explicit iterations so body-scope variables (e.g. counters)
806822 // account for the extra iterations while keeping the lower bound
807823 // established by the non-optional explicit keys.
808- if ($ hasUnsealed ) {
824+ if ($ hasUnsealed && $ chainEndScope !== null && $ endScope !== null ) {
809825 $ loopScope = $ endScope ;
810826 $ count = 0 ;
811827 do {
@@ -814,13 +830,15 @@ private function tryProcessUnrolledConstantArrayForeach(
814830 $ iterBodyScope = $ loopScope ->mergeWith ($ endScope );
815831 $ iterBodyScope = $ this ->enterForeach ($ nodeScopeResolver , $ iterBodyScope , $ iterStorage , $ originalScope , $ stmt , $ iterateeType , $ nativeIterateeType , new NoopNodeCallback ());
816832 $ iterBodyScopeResult = $ nodeScopeResolver ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ iterBodyScope , $ iterStorage , new NoopNodeCallback (), $ context ->enterDeep ()->withoutTemplateArgumentResolution ())->filterOutLoopExitPoints ();
817- $ loopScope = $ iterBodyScopeResult ->getScope ();
818- foreach ($ iterBodyScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
819- $ loopScope = $ loopScope ->mergeWith ($ continueExitPoint ->getScope ());
820- }
833+ $ backEdgeScope = $ iterBodyScopeResult ->getLoopBackEdgeScope ();
821834 foreach ($ iterBodyScopeResult ->getExitPointsByType (Break_::class) as $ breakExitPoint ) {
822835 $ endScope = $ endScope ->mergeWith ($ breakExitPoint ->getScope ());
823836 }
837+ if ($ backEdgeScope === null ) {
838+ $ loopScope = $ prevLoopScope ;
839+ break ;
840+ }
841+ $ loopScope = $ backEdgeScope ;
824842 $ bodyScope = $ bodyScope ->mergeWith ($ loopScope );
825843 if ($ loopScope ->equals ($ prevLoopScope )) {
826844 break ;
0 commit comments