@@ -1288,15 +1288,17 @@ public function processStmtNode(
12881288 $ bodyScope = $ bodyScope ->mergeWith ($ this ->polluteScopeWithAlwaysIterableForeach ? $ scope ->filterByTruthyValue ($ arrayComparisonExpr ) : $ scope );
12891289 $ storage = $ originalStorage ;
12901290 $ bodyScope = $ this ->enterForeach ($ bodyScope , $ storage , $ originalScope , $ stmt , $ nodeCallback );
1291- $ finalScopeResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
1292- $ finalScope = $ finalScopeResult ->getScope ();
1291+ $ rawFinalScopeResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context );
1292+ $ foreachBodyAlwaysTerminated = $ rawFinalScopeResult ->isAlwaysTerminating ();
1293+ $ finalScopeResult = $ rawFinalScopeResult ->filterOutLoopExitPoints ();
1294+ $ finalScope = $ foreachBodyAlwaysTerminated ? null : $ finalScopeResult ->getScope ();
12931295 $ scopesWithIterableValueType = [];
12941296
12951297 $ originalKeyVarExpr = null ;
12961298 $ continueExitPointHasUnoriginalKeyType = false ;
12971299 if ($ stmt ->keyVar instanceof Variable && is_string ($ stmt ->keyVar ->name )) {
12981300 $ originalKeyVarExpr = new OriginalForeachKeyExpr ($ stmt ->keyVar ->name );
1299- if ($ finalScope ->hasExpressionType ($ originalKeyVarExpr )->yes ()) {
1301+ if ($ finalScope !== null && $ finalScope ->hasExpressionType ($ originalKeyVarExpr )->yes ()) {
13001302 $ scopesWithIterableValueType [] = $ finalScope ;
13011303 } else {
13021304 $ continueExitPointHasUnoriginalKeyType = true ;
@@ -1305,16 +1307,29 @@ public function processStmtNode(
13051307
13061308 foreach ($ finalScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
13071309 $ continueScope = $ continueExitPoint ->getScope ();
1308- $ finalScope = $ continueScope ->mergeWith ($ finalScope );
1310+ if ($ finalScope === null ) {
1311+ $ finalScope = $ continueScope ;
1312+ } else {
1313+ $ finalScope = $ continueScope ->mergeWith ($ finalScope );
1314+ }
13091315 if ($ originalKeyVarExpr === null || !$ continueScope ->hasExpressionType ($ originalKeyVarExpr )->yes ()) {
13101316 $ continueExitPointHasUnoriginalKeyType = true ;
13111317 continue ;
13121318 }
13131319 $ scopesWithIterableValueType [] = $ continueScope ;
13141320 }
13151321 $ breakExitPoints = $ finalScopeResult ->getExitPointsByType (Break_::class);
1316- foreach ($ breakExitPoints as $ breakExitPoint ) {
1317- $ finalScope = $ breakExitPoint ->getScope ()->mergeWith ($ finalScope );
1322+ if ($ finalScope !== null ) {
1323+ foreach ($ breakExitPoints as $ breakExitPoint ) {
1324+ $ finalScope = $ breakExitPoint ->getScope ()->mergeWith ($ finalScope );
1325+ }
1326+ } elseif (count ($ breakExitPoints ) > 0 ) {
1327+ $ finalScope = $ breakExitPoints [0 ]->getScope ();
1328+ for ($ i = 1 , $ c = count ($ breakExitPoints ); $ i < $ c ; $ i ++) {
1329+ $ finalScope = $ breakExitPoints [$ i ]->getScope ()->mergeWith ($ finalScope );
1330+ }
1331+ } else {
1332+ $ finalScope = $ finalScopeResult ->getScope ();
13181333 }
13191334
13201335 if ($ unrolledEndScope !== null ) {
@@ -1514,7 +1529,9 @@ public function processStmtNode(
15141529 $ bodyScopeMaybeRan = $ bodyScope ;
15151530 $ storage = $ originalStorage ;
15161531 $ bodyScope = $ this ->processExprNode ($ stmt , $ stmt ->cond , $ bodyScope , $ storage , $ nodeCallback , ExpressionContext::createDeep ())->getTruthyScope ();
1517- $ finalScopeResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
1532+ $ rawWhileBodyResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context );
1533+ $ whileBodyAlwaysTerminated = $ rawWhileBodyResult ->isAlwaysTerminating ();
1534+ $ finalScopeResult = $ rawWhileBodyResult ->filterOutLoopExitPoints ();
15181535 $ finalScope = $ finalScopeResult ->getScope ()->filterByFalseyValue ($ stmt ->cond );
15191536
15201537 $ alwaysIterates = false ;
@@ -1532,7 +1549,7 @@ public function processStmtNode(
15321549
15331550 $ breakExitPoints = $ finalScopeResult ->getExitPointsByType (Break_::class);
15341551 if (count ($ breakExitPoints ) > 0 ) {
1535- $ breakScope = $ alwaysIterates ? null : $ finalScope ;
1552+ $ breakScope = $ alwaysIterates || $ whileBodyAlwaysTerminated ? null : $ finalScope ;
15361553 foreach ($ breakExitPoints as $ breakExitPoint ) {
15371554 $ breakScope = $ breakScope === null ? $ breakExitPoint ->getScope () : $ breakScope ->mergeWith ($ breakExitPoint ->getScope ());
15381555 }
@@ -1610,7 +1627,9 @@ public function processStmtNode(
16101627 }
16111628
16121629 $ storage = $ originalStorage ;
1613- $ bodyScopeResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
1630+ $ rawDoWhileBodyResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context );
1631+ $ doWhileBodyAlwaysTerminated = $ rawDoWhileBodyResult ->isAlwaysTerminating ();
1632+ $ bodyScopeResult = $ rawDoWhileBodyResult ->filterOutLoopExitPoints ();
16141633 $ bodyScope = $ bodyScopeResult ->getScope ();
16151634 foreach ($ bodyScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
16161635 $ bodyScope = $ bodyScope ->mergeWith ($ continueExitPoint ->getScope ());
@@ -1645,7 +1664,7 @@ public function processStmtNode(
16451664
16461665 $ breakExitPoints = $ bodyScopeResult ->getExitPointsByType (Break_::class);
16471666 if (count ($ breakExitPoints ) > 0 ) {
1648- $ breakScope = $ alwaysIterates ? null : $ finalScope ;
1667+ $ breakScope = $ alwaysIterates || $ doWhileBodyAlwaysTerminated ? null : $ finalScope ;
16491668 foreach ($ breakExitPoints as $ breakExitPoint ) {
16501669 $ breakScope = $ breakScope === null ? $ breakExitPoint ->getScope () : $ breakScope ->mergeWith ($ breakExitPoint ->getScope ());
16511670 }
@@ -1744,7 +1763,9 @@ public function processStmtNode(
17441763 $ bodyScope = $ this ->inferForLoopExpressions ($ stmt , $ lastCondExpr , $ bodyScope );
17451764 }
17461765
1747- $ finalScopeResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context )->filterOutLoopExitPoints ();
1766+ $ rawForBodyResult = $ this ->processStmtNodesInternal ($ stmt , $ stmt ->stmts , $ bodyScope , $ storage , $ nodeCallback , $ context );
1767+ $ forBodyAlwaysTerminated = $ rawForBodyResult ->isAlwaysTerminating ();
1768+ $ finalScopeResult = $ rawForBodyResult ->filterOutLoopExitPoints ();
17481769 $ finalScope = $ finalScopeResult ->getScope ();
17491770 foreach ($ finalScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
17501771 $ finalScope = $ continueExitPoint ->getScope ()->mergeWith ($ finalScope );
@@ -1762,7 +1783,7 @@ public function processStmtNode(
17621783
17631784 $ breakExitPoints = $ finalScopeResult ->getExitPointsByType (Break_::class);
17641785 if (count ($ breakExitPoints ) > 0 ) {
1765- $ breakScope = $ alwaysIterates ->yes () ? null : $ finalScope ;
1786+ $ breakScope = $ alwaysIterates ->yes () || $ forBodyAlwaysTerminated ? null : $ finalScope ;
17661787 foreach ($ breakExitPoints as $ breakExitPoint ) {
17671788 $ breakScope = $ breakScope === null ? $ breakExitPoint ->getScope () : $ breakScope ->mergeWith ($ breakExitPoint ->getScope ());
17681789 }
@@ -3921,6 +3942,7 @@ private function tryProcessUnrolledConstantArrayForeach(
39213942 $ chainScope = $ originalScope ;
39223943 $ entryScopes = [];
39233944 $ breakScopes = [];
3945+ $ hasNormalCompletion = false ;
39243946 foreach ($ keyTypes as $ i => $ keyType ) {
39253947 $ valueType = $ valueTypes [$ i ];
39263948 $ isOptional = isset ($ optionalKeys [$ i ]);
@@ -3965,28 +3987,39 @@ private function tryProcessUnrolledConstantArrayForeach(
39653987 $ entryScopes [] = $ iterScope ;
39663988
39673989 $ iterStorage = $ originalStorage ->duplicate ();
3968- $ bodyResult = $ this ->processStmtNodesInternal (
3990+ $ rawBodyResult = $ this ->processStmtNodesInternal (
39693991 $ stmt ,
39703992 $ stmt ->stmts ,
39713993 $ iterScope ,
39723994 $ iterStorage ,
39733995 new NoopNodeCallback (),
39743996 $ context ->enterDeep (),
3975- )->filterOutLoopExitPoints ();
3997+ );
3998+ $ bodyAlwaysTerminated = $ rawBodyResult ->isAlwaysTerminating ();
3999+ $ bodyResult = $ rawBodyResult ->filterOutLoopExitPoints ();
39764000
3977- $ iterEndScope = $ bodyResult ->getScope ();
4001+ $ iterEndScope = $ bodyAlwaysTerminated ? null : $ bodyResult ->getScope ();
39784002 foreach ($ bodyResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
3979- $ iterEndScope = $ iterEndScope ->mergeWith ($ continueExitPoint ->getScope ());
4003+ if ($ iterEndScope === null ) {
4004+ $ iterEndScope = $ continueExitPoint ->getScope ();
4005+ } else {
4006+ $ iterEndScope = $ iterEndScope ->mergeWith ($ continueExitPoint ->getScope ());
4007+ }
39804008 }
39814009 foreach ($ bodyResult ->getExitPointsByType (Break_::class) as $ breakExitPoint ) {
39824010 $ breakScopes [] = $ breakExitPoint ->getScope ();
39834011 }
39844012
4013+ if ($ iterEndScope === null ) {
4014+ continue ;
4015+ }
4016+
39854017 if ($ isOptional ) {
39864018 $ chainScope = $ iterEndScope ->mergeWith ($ chainScope );
39874019 } else {
39884020 $ chainScope = $ iterEndScope ;
39894021 }
4022+ $ hasNormalCompletion = true ;
39904023 }
39914024
39924025 $ bodyScope = $ entryScopes [0 ];
@@ -4002,8 +4035,15 @@ private function tryProcessUnrolledConstantArrayForeach(
40024035 $ bodyScope = $ bodyScope ->mergeWith ($ chainScope );
40034036 }
40044037
4005- foreach ($ breakScopes as $ breakScope ) {
4006- $ chainScope = $ chainScope ->mergeWith ($ breakScope );
4038+ if ($ hasNormalCompletion ) {
4039+ foreach ($ breakScopes as $ breakScope ) {
4040+ $ chainScope = $ chainScope ->mergeWith ($ breakScope );
4041+ }
4042+ } elseif (count ($ breakScopes ) > 0 ) {
4043+ $ chainScope = $ breakScopes [0 ];
4044+ for ($ i = 1 , $ c = count ($ breakScopes ); $ i < $ c ; $ i ++) {
4045+ $ chainScope = $ chainScope ->mergeWith ($ breakScopes [$ i ]);
4046+ }
40074047 }
40084048
40094049 return ['bodyScope ' => $ bodyScope , 'endScope ' => $ chainScope ];
0 commit comments