@@ -278,22 +278,76 @@ public function processNodes(
278278 {
279279 $ expressionResultStorage = new ExpressionResultStorage ();
280280 $ alreadyTerminated = false ;
281+ $ exitPoints = [];
282+
283+ $ stmts = [];
284+ $ stmtToNodeIndex = [];
281285 foreach ($ nodes as $ i => $ node ) {
282- if (
283- !$ node instanceof Node \Stmt
284- || ($ alreadyTerminated && !($ node instanceof Node \Stmt \Function_ || $ node instanceof Node \Stmt \ClassLike))
285- ) {
286+ if (!($ node instanceof Node \Stmt)) {
286287 continue ;
287288 }
288289
290+ $ stmtToNodeIndex [count ($ stmts )] = $ i ;
291+ $ stmts [] = $ node ;
292+ }
293+
294+ $ dummyParent = new Node \Stmt \Nop ();
295+ foreach ($ stmts as $ si => $ node ) {
296+ if ($ alreadyTerminated && !($ node instanceof Node \Stmt \Function_ || $ node instanceof Node \Stmt \ClassLike || $ node instanceof Node \Stmt \Label)) {
297+ continue ;
298+ }
299+
300+ $ nestedLabelNames = $ node ->getAttribute (GotoLabelVisitor::NESTED_BACKWARD_GOTO_LABELS_ATTRIBUTE );
301+ if ($ nestedLabelNames !== null ) {
302+ $ scope = $ this ->resolveBackwardGotoScope (
303+ $ dummyParent ,
304+ [$ node ],
305+ $ scope ,
306+ $ expressionResultStorage ,
307+ StatementContext::createDeep (),
308+ static fn (string $ name ): bool => isset ($ nestedLabelNames [$ name ]),
309+ false ,
310+ );
311+ }
312+
289313 $ statementResult = $ this ->processStmtNode ($ node , $ scope , $ expressionResultStorage , $ nodeCallback , StatementContext::createTopLevel ());
290314 $ scope = $ statementResult ->getScope ();
315+
316+ if ($ node instanceof Node \Stmt \Label) {
317+ $ labelName = $ node ->name ->toString ();
318+
319+ [$ scope , $ alreadyTerminated , $ exitPoints ] = $ this ->mergeForwardGotoExitPoints (
320+ $ labelName ,
321+ $ scope ,
322+ $ alreadyTerminated ,
323+ $ exitPoints ,
324+ );
325+
326+ if ($ alreadyTerminated ) {
327+ continue ;
328+ }
329+
330+ if ($ node ->getAttribute (GotoLabelVisitor::HAS_BACKWARD_GOTO_ATTRIBUTE ) === true ) {
331+ $ scope = $ this ->resolveBackwardGotoScope (
332+ $ dummyParent ,
333+ array_slice ($ stmts , $ si + 1 ),
334+ $ scope ,
335+ $ expressionResultStorage ,
336+ StatementContext::createDeep (),
337+ static fn (string $ name ): bool => $ name === $ labelName ,
338+ true ,
339+ );
340+ }
341+ }
342+
343+ $ exitPoints = array_merge ($ exitPoints , $ statementResult ->getExitPoints ());
344+
291345 if ($ alreadyTerminated || !$ statementResult ->isAlwaysTerminating ()) {
292346 continue ;
293347 }
294348
295349 $ alreadyTerminated = true ;
296- $ nextStmts = $ this ->getNextUnreachableStatements (array_slice ($ nodes , $ i + 1 ), true );
350+ $ nextStmts = $ this ->getNextUnreachableStatements (array_slice ($ nodes , $ stmtToNodeIndex [ $ si ] + 1 ), true );
297351 $ this ->processUnreachableStatement ($ nextStmts , $ scope , $ expressionResultStorage , $ nodeCallback );
298352 }
299353
@@ -308,6 +362,93 @@ protected function processPendingFibers(ExpressionResultStorage $storage): void
308362 {
309363 }
310364
365+ /**
366+ * @param Node\Stmt[] $bodyStmts
367+ * @param Closure(string): bool $gotoNameMatcher
368+ */
369+ private function resolveBackwardGotoScope (
370+ Node $ parentNode ,
371+ array $ bodyStmts ,
372+ MutatingScope $ scope ,
373+ ExpressionResultStorage $ storage ,
374+ StatementContext $ context ,
375+ Closure $ gotoNameMatcher ,
376+ bool $ mergeBodyScopeEachIteration ,
377+ ): MutatingScope
378+ {
379+ $ bodyScope = $ scope ;
380+ $ count = 0 ;
381+ do {
382+ $ prevScope = $ bodyScope ;
383+ if ($ mergeBodyScopeEachIteration ) {
384+ $ bodyScope = $ bodyScope ->mergeWith ($ scope );
385+ }
386+ $ tempStorage = $ storage ->duplicate ();
387+ $ bodyScopeResult = $ this ->processStmtNodesInternal (
388+ $ parentNode ,
389+ $ bodyStmts ,
390+ $ bodyScope ,
391+ $ tempStorage ,
392+ new NoopNodeCallback (),
393+ $ context ,
394+ );
395+
396+ $ gotoScope = null ;
397+ foreach ($ bodyScopeResult ->getExitPoints () as $ ep ) {
398+ $ epStmt = $ ep ->getStatement ();
399+ if (!($ epStmt instanceof Goto_) || !$ gotoNameMatcher ($ epStmt ->name ->toString ())) {
400+ continue ;
401+ }
402+
403+ $ gotoScope = $ gotoScope === null ? $ ep ->getScope () : $ gotoScope ->mergeWith ($ ep ->getScope ());
404+ }
405+
406+ if ($ gotoScope !== null ) {
407+ $ bodyScope = $ scope ->mergeWith ($ gotoScope );
408+ }
409+
410+ if ($ bodyScope ->equals ($ prevScope )) {
411+ break ;
412+ }
413+
414+ if ($ count >= self ::GENERALIZE_AFTER_ITERATION ) {
415+ $ bodyScope = $ prevScope ->generalizeWith ($ bodyScope );
416+ }
417+ $ count ++;
418+ } while ($ count < self ::LOOP_SCOPE_ITERATIONS );
419+
420+ return $ bodyScope ;
421+ }
422+
423+ /**
424+ * @param InternalStatementExitPoint[] $exitPoints
425+ * @return array{MutatingScope, bool, list<InternalStatementExitPoint>}
426+ */
427+ private function mergeForwardGotoExitPoints (
428+ string $ labelName ,
429+ MutatingScope $ scope ,
430+ bool $ alreadyTerminated ,
431+ array $ exitPoints ,
432+ ): array
433+ {
434+ $ newExitPoints = [];
435+ foreach ($ exitPoints as $ exitPoint ) {
436+ $ exitStmt = $ exitPoint ->getStatement ();
437+ if ($ exitStmt instanceof Goto_ && $ exitStmt ->name ->toString () === $ labelName ) {
438+ if ($ alreadyTerminated ) {
439+ $ scope = $ exitPoint ->getScope ();
440+ $ alreadyTerminated = false ;
441+ } else {
442+ $ scope = $ scope ->mergeWith ($ exitPoint ->getScope ());
443+ }
444+ } else {
445+ $ newExitPoints [] = $ exitPoint ;
446+ }
447+ }
448+
449+ return [$ scope , $ alreadyTerminated , $ newExitPoints ];
450+ }
451+
311452 /**
312453 * @param Node\Stmt[] $nextStmts
313454 * @param callable(Node $node, Scope $scope): void $nodeCallback
@@ -420,47 +561,15 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
420561
421562 $ nestedLabelNames = $ stmt ->getAttribute (GotoLabelVisitor::NESTED_BACKWARD_GOTO_LABELS_ATTRIBUTE );
422563 if ($ nestedLabelNames !== null && $ context ->isTopLevel ()) {
423- $ originalStorage = $ storage ;
424- $ bodyScope = $ scope ;
425- $ count = 0 ;
426- do {
427- $ prevScope = $ bodyScope ;
428- $ tempStorage = $ originalStorage ->duplicate ();
429- $ bodyScopeResult = $ this ->processStmtNodesInternal (
430- $ parentNode ,
431- [$ stmt ],
432- $ bodyScope ,
433- $ tempStorage ,
434- new NoopNodeCallback (),
435- $ context ->enterDeep (),
436- );
437-
438- $ gotoScope = null ;
439- foreach ($ bodyScopeResult ->getExitPoints () as $ ep ) {
440- $ epStmt = $ ep ->getStatement ();
441- if (!($ epStmt instanceof Goto_) || !isset ($ nestedLabelNames [$ epStmt ->name ->toString ()])) {
442- continue ;
443- }
444-
445- $ gotoScope = $ gotoScope === null ? $ ep ->getScope () : $ gotoScope ->mergeWith ($ ep ->getScope ());
446- }
447-
448- if ($ gotoScope !== null ) {
449- $ bodyScope = $ scope ->mergeWith ($ gotoScope );
450- }
451-
452- if ($ bodyScope ->equals ($ prevScope )) {
453- break ;
454- }
455-
456- if ($ count >= self ::GENERALIZE_AFTER_ITERATION ) {
457- $ bodyScope = $ prevScope ->generalizeWith ($ bodyScope );
458- }
459- $ count ++;
460- } while ($ count < self ::LOOP_SCOPE_ITERATIONS );
461-
462- $ scope = $ bodyScope ;
463- $ storage = $ originalStorage ;
564+ $ scope = $ this ->resolveBackwardGotoScope (
565+ $ parentNode ,
566+ [$ stmt ],
567+ $ scope ,
568+ $ storage ,
569+ $ context ->enterDeep (),
570+ static fn (string $ name ): bool => isset ($ nestedLabelNames [$ name ]),
571+ false ,
572+ );
464573 }
465574
466575 $ statementResult = $ this ->processStmtNode (
@@ -476,70 +585,27 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
476585 if ($ stmt instanceof Node \Stmt \Label) {
477586 $ labelName = $ stmt ->name ->toString ();
478587
479- $ newExitPoints = [];
480- foreach ($ exitPoints as $ exitPoint ) {
481- $ exitStmt = $ exitPoint ->getStatement ();
482- if ($ exitStmt instanceof Goto_ && $ exitStmt ->name ->toString () === $ labelName ) {
483- if ($ alreadyTerminated ) {
484- $ scope = $ exitPoint ->getScope ();
485- $ alreadyTerminated = false ;
486- } else {
487- $ scope = $ scope ->mergeWith ($ exitPoint ->getScope ());
488- }
489- } else {
490- $ newExitPoints [] = $ exitPoint ;
491- }
492- }
493- $ exitPoints = $ newExitPoints ;
588+ [$ scope , $ alreadyTerminated , $ exitPoints ] = $ this ->mergeForwardGotoExitPoints (
589+ $ labelName ,
590+ $ scope ,
591+ $ alreadyTerminated ,
592+ $ exitPoints ,
593+ );
494594
495595 if ($ alreadyTerminated ) {
496596 continue ;
497597 }
498598
499599 if ($ stmt ->getAttribute (GotoLabelVisitor::HAS_BACKWARD_GOTO_ATTRIBUTE ) === true && $ context ->isTopLevel ()) {
500- $ originalStorage = $ storage ;
501- $ bodyStmts = array_slice ($ stmts , $ i + 1 );
502- $ bodyScope = $ scope ;
503- $ count = 0 ;
504- do {
505- $ prevScope = $ bodyScope ;
506- $ bodyScope = $ bodyScope ->mergeWith ($ scope );
507- $ tempStorage = $ originalStorage ->duplicate ();
508- $ bodyScopeResult = $ this ->processStmtNodesInternal (
509- $ parentNode ,
510- $ bodyStmts ,
511- $ bodyScope ,
512- $ tempStorage ,
513- new NoopNodeCallback (),
514- $ context ->enterDeep (),
515- );
516-
517- $ gotoScope = null ;
518- foreach ($ bodyScopeResult ->getExitPoints () as $ ep ) {
519- $ epStmt = $ ep ->getStatement ();
520- if (!($ epStmt instanceof Goto_) || $ epStmt ->name ->toString () !== $ labelName ) {
521- continue ;
522- }
523-
524- $ gotoScope = $ gotoScope === null ? $ ep ->getScope () : $ gotoScope ->mergeWith ($ ep ->getScope ());
525- }
526-
527- if ($ gotoScope !== null ) {
528- $ bodyScope = $ scope ->mergeWith ($ gotoScope );
529- }
530-
531- if ($ bodyScope ->equals ($ prevScope )) {
532- break ;
533- }
534-
535- if ($ count >= self ::GENERALIZE_AFTER_ITERATION ) {
536- $ bodyScope = $ prevScope ->generalizeWith ($ bodyScope );
537- }
538- $ count ++;
539- } while ($ count < self ::LOOP_SCOPE_ITERATIONS );
540-
541- $ scope = $ bodyScope ;
542- $ storage = $ originalStorage ;
600+ $ scope = $ this ->resolveBackwardGotoScope (
601+ $ parentNode ,
602+ array_slice ($ stmts , $ i + 1 ),
603+ $ scope ,
604+ $ storage ,
605+ $ context ->enterDeep (),
606+ static fn (string $ name ): bool => $ name === $ labelName ,
607+ true ,
608+ );
543609 }
544610 }
545611
0 commit comments