3838use PhpParser \Node \Stmt \Echo_ ;
3939use PhpParser \Node \Stmt \For_ ;
4040use PhpParser \Node \Stmt \Foreach_ ;
41+ use PhpParser \Node \Stmt \Goto_ ;
4142use PhpParser \Node \Stmt \If_ ;
4243use PhpParser \Node \Stmt \InlineHTML ;
4344use PhpParser \Node \Stmt \Return_ ;
110111use PHPStan \Node \VarTagChangedExpressionTypeNode ;
111112use PHPStan \Parser \ArrowFunctionArgVisitor ;
112113use PHPStan \Parser \ClosureArgVisitor ;
114+ use PHPStan \Parser \GotoLabelVisitor ;
113115use PHPStan \Parser \ImmediatelyInvokedClosureVisitor ;
114116use PHPStan \Parser \LineAttributesVisitor ;
115117use PHPStan \Parser \Parser ;
@@ -408,12 +410,59 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
408410 || $ parentNode instanceof Node \Stmt \ClassMethod
409411 || $ parentNode instanceof PropertyHookStatementNode
410412 || $ parentNode instanceof Expr \Closure;
413+
411414 foreach ($ stmts as $ i => $ stmt ) {
412- if ($ alreadyTerminated && !($ stmt instanceof Node \Stmt \Function_ || $ stmt instanceof Node \Stmt \ClassLike)) {
415+ if ($ alreadyTerminated && !($ stmt instanceof Node \Stmt \Function_ || $ stmt instanceof Node \Stmt \ClassLike || $ stmt instanceof Node \ Stmt \Label )) {
413416 continue ;
414417 }
415418
416419 $ isLast = $ i === $ stmtCount - 1 ;
420+
421+ $ nestedLabelNames = $ stmt ->getAttribute (GotoLabelVisitor::NESTED_BACKWARD_GOTO_LABELS_ATTRIBUTE );
422+ 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 ;
464+ }
465+
417466 $ statementResult = $ this ->processStmtNode (
418467 $ stmt ,
419468 $ scope ,
@@ -424,6 +473,76 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
424473 $ scope = $ statementResult ->getScope ();
425474 $ hasYield = $ hasYield || $ statementResult ->hasYield ();
426475
476+ if ($ stmt instanceof Node \Stmt \Label) {
477+ $ labelName = $ stmt ->name ->toString ();
478+
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 ;
494+
495+ if ($ alreadyTerminated ) {
496+ continue ;
497+ }
498+
499+ 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 ;
543+ }
544+ }
545+
427546 if ($ shouldCheckLastStatement && $ isLast ) {
428547 $ endStatements = $ statementResult ->getEndStatements ();
429548 if (count ($ endStatements ) > 0 ) {
@@ -917,6 +1036,18 @@ public function processStmtNode(
9171036 return new InternalStatementResult ($ scope , $ hasYield , true , [
9181037 new InternalStatementExitPoint ($ stmt , $ scope ),
9191038 ], $ overridingThrowPoints ?? $ throwPoints , $ impurePoints );
1039+ } elseif ($ stmt instanceof Goto_) {
1040+ $ hasYield = false ;
1041+ $ throwPoints = [];
1042+ $ impurePoints = [];
1043+
1044+ return new InternalStatementResult ($ scope , $ hasYield , true , [
1045+ new InternalStatementExitPoint ($ stmt , $ scope ),
1046+ ], $ overridingThrowPoints ?? $ throwPoints , $ impurePoints );
1047+ } elseif ($ stmt instanceof Node \Stmt \Label) {
1048+ $ hasYield = false ;
1049+ $ throwPoints = $ overridingThrowPoints ?? [];
1050+ $ impurePoints = [];
9201051 } elseif ($ stmt instanceof Node \Stmt \Expression) {
9211052 if ($ stmt ->expr instanceof Expr \Throw_) {
9221053 $ scope = $ stmtScope ;
@@ -4800,6 +4931,9 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding):
48004931 $ stmts = [];
48014932 $ isPassedUnreachableStatement = false ;
48024933 foreach ($ nodes as $ node ) {
4934+ if ($ node instanceof Node \Stmt \Label) {
4935+ break ;
4936+ }
48034937 if ($ earlyBinding && ($ node instanceof Node \Stmt \Function_ || $ node instanceof Node \Stmt \ClassLike || $ node instanceof Node \Stmt \HaltCompiler)) {
48044938 continue ;
48054939 }
0 commit comments