@@ -2413,13 +2413,13 @@ public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scop
24132413 return $ this ->processExprNode ($ stmt , $ newExpr , $ scope , $ nodeCallback , $ context );
24142414 }
24152415
2416- $ isAlwaysTerminating = false ;
24172416 $ this ->callNodeCallbackWithExpression ($ nodeCallback , $ expr , $ scope , $ context );
24182417
24192418 if ($ expr instanceof Variable) {
24202419 $ hasYield = false ;
24212420 $ throwPoints = [];
24222421 $ impurePoints = [];
2422+ $ isAlwaysTerminating = false ;
24232423 if ($ expr ->name instanceof Expr) {
24242424 return $ this ->processExprNode ($ stmt , $ expr ->name , $ scope , $ nodeCallback , $ context ->enterDeep ());
24252425 } elseif (in_array ($ expr ->name , Scope::SUPERGLOBAL_VARIABLES , true )) {
@@ -2537,6 +2537,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp
25372537 $ functionReflection = null ;
25382538 $ throwPoints = [];
25392539 $ impurePoints = [];
2540+ $ isAlwaysTerminating = false ;
25402541 if ($ expr ->name instanceof Expr) {
25412542 $ nameType = $ scope ->getType ($ expr ->name );
25422543 if (!$ nameType ->isCallable ()->no ()) {
@@ -2941,6 +2942,7 @@ static function (): void {
29412942 $ hasYield = false ;
29422943 $ throwPoints = [];
29432944 $ impurePoints = [];
2945+ $ isAlwaysTerminating = false ;
29442946 if ($ expr ->class instanceof Expr) {
29452947 $ objectClasses = $ scope ->getType ($ expr ->class )->getObjectClassNames ();
29462948 if (count ($ objectClasses ) !== 1 ) {
@@ -3104,6 +3106,7 @@ static function (): void {
31043106 $ hasYield = $ result ->hasYield ();
31053107 $ throwPoints = $ result ->getThrowPoints ();
31063108 $ impurePoints = $ result ->getImpurePoints ();
3109+ $ isAlwaysTerminating = false ;
31073110 $ scope = $ result ->getScope ();
31083111 if ($ expr ->name instanceof Expr) {
31093112 $ result = $ this ->processExprNode ($ stmt , $ expr ->name , $ scope , $ nodeCallback , $ context ->enterDeep ());
@@ -3152,6 +3155,7 @@ static function (): void {
31523155 true ,
31533156 ),
31543157 ];
3158+ $ isAlwaysTerminating = false ;
31553159 if ($ expr ->class instanceof Expr) {
31563160 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
31573161 $ hasYield = $ result ->hasYield ();
@@ -3190,6 +3194,7 @@ static function (): void {
31903194 $ hasYield = $ result ->hasYield ();
31913195 $ throwPoints = $ result ->getThrowPoints ();
31923196 $ impurePoints = $ result ->getImpurePoints ();
3197+ $ isAlwaysTerminating = false ;
31933198 $ scope = $ result ->getScope ();
31943199 } elseif ($ expr instanceof Exit_) {
31953200 $ hasYield = false ;
@@ -3211,6 +3216,7 @@ static function (): void {
32113216 $ hasYield = false ;
32123217 $ throwPoints = [];
32133218 $ impurePoints = [];
3219+ $ isAlwaysTerminating = false ;
32143220 foreach ($ expr ->parts as $ part ) {
32153221 if (!$ part instanceof Expr) {
32163222 continue ;
@@ -3226,6 +3232,7 @@ static function (): void {
32263232 $ hasYield = false ;
32273233 $ throwPoints = [];
32283234 $ impurePoints = [];
3235+ $ isAlwaysTerminating = false ;
32293236 if ($ expr ->dim !== null ) {
32303237 $ result = $ this ->processExprNode ($ stmt , $ expr ->dim , $ scope , $ nodeCallback , $ context ->enterDeep ());
32313238 $ hasYield = $ result ->hasYield ();
@@ -3244,6 +3251,7 @@ static function (): void {
32443251 $ hasYield = false ;
32453252 $ throwPoints = [];
32463253 $ impurePoints = [];
3254+ $ isAlwaysTerminating = false ;
32473255 foreach ($ expr ->items as $ arrayItem ) {
32483256 $ itemNodes [] = new LiteralArrayItem ($ scope , $ arrayItem );
32493257 $ nodeCallback ($ arrayItem , $ scope );
@@ -3325,6 +3333,7 @@ static function (): void {
33253333 $ hasYield = $ condResult ->hasYield () || $ rightResult ->hasYield ();
33263334 $ throwPoints = array_merge ($ condResult ->getThrowPoints (), $ rightResult ->getThrowPoints ());
33273335 $ impurePoints = array_merge ($ condResult ->getImpurePoints (), $ rightResult ->getImpurePoints ());
3336+ $ isAlwaysTerminating = false ;
33283337 } elseif ($ expr instanceof BinaryOp) {
33293338 $ result = $ this ->processExprNode ($ stmt , $ expr ->left , $ scope , $ nodeCallback , $ context ->enterDeep ());
33303339 $ scope = $ result ->getScope ();
@@ -3357,11 +3366,13 @@ static function (): void {
33573366 true ,
33583367 );
33593368 $ hasYield = $ result ->hasYield ();
3369+ $ isAlwaysTerminating = false ;
33603370 $ scope = $ result ->getScope ()->afterExtractCall ();
33613371 } elseif ($ expr instanceof Expr \Print_) {
33623372 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33633373 $ throwPoints = $ result ->getThrowPoints ();
33643374 $ impurePoints = $ result ->getImpurePoints ();
3375+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
33653376 $ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'print ' , 'print ' , true );
33663377 $ hasYield = $ result ->hasYield ();
33673378
@@ -3370,6 +3381,7 @@ static function (): void {
33703381 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33713382 $ throwPoints = $ result ->getThrowPoints ();
33723383 $ impurePoints = $ result ->getImpurePoints ();
3384+ $ isAlwaysTerminating = true ;
33733385 $ hasYield = $ result ->hasYield ();
33743386
33753387 $ exprType = $ scope ->getType ($ expr ->expr );
@@ -3397,6 +3409,7 @@ static function (): void {
33973409 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33983410 $ throwPoints = $ result ->getThrowPoints ();
33993411 $ impurePoints = $ result ->getImpurePoints ();
3412+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34003413 $ hasYield = $ result ->hasYield ();
34013414
34023415 $ scope = $ result ->getScope ();
@@ -3407,6 +3420,7 @@ static function (): void {
34073420 $ impurePoints = $ result ->getImpurePoints ();
34083421 $ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'eval ' , 'eval ' , true );
34093422 $ hasYield = $ result ->hasYield ();
3423+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34103424
34113425 $ scope = $ result ->getScope ();
34123426 } elseif ($ expr instanceof Expr \YieldFrom) {
@@ -3422,6 +3436,7 @@ static function (): void {
34223436 true ,
34233437 );
34243438 $ hasYield = true ;
3439+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34253440
34263441 $ scope = $ result ->getScope ();
34273442 } elseif ($ expr instanceof BooleanNot) {
@@ -3430,7 +3445,10 @@ static function (): void {
34303445 $ hasYield = $ result ->hasYield ();
34313446 $ throwPoints = $ result ->getThrowPoints ();
34323447 $ impurePoints = $ result ->getImpurePoints ();
3448+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34333449 } elseif ($ expr instanceof Expr \ClassConstFetch) {
3450+ $ isAlwaysTerminating = false ;
3451+
34343452 if ($ expr ->class instanceof Expr) {
34353453 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
34363454 $ scope = $ result ->getScope ();
@@ -3461,13 +3479,15 @@ static function (): void {
34613479 $ hasYield = $ result ->hasYield ();
34623480 $ throwPoints = $ result ->getThrowPoints ();
34633481 $ impurePoints = $ result ->getImpurePoints ();
3482+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34643483 $ scope = $ this ->revertNonNullability ($ scope , $ nonNullabilityResult ->getSpecifiedExpressions ());
34653484 $ scope = $ this ->lookForUnsetAllowedUndefinedExpressions ($ scope , $ expr ->expr );
34663485 } elseif ($ expr instanceof Expr \Isset_) {
34673486 $ hasYield = false ;
34683487 $ throwPoints = [];
34693488 $ impurePoints = [];
34703489 $ nonNullabilityResults = [];
3490+ $ isAlwaysTerminating = false ;
34713491 foreach ($ expr ->vars as $ var ) {
34723492 $ nonNullabilityResult = $ this ->ensureNonNullability ($ scope , $ var );
34733493 $ scope = $ this ->lookForSetAllowedUndefinedExpressions ($ nonNullabilityResult ->getScope (), $ var );
@@ -3476,6 +3496,7 @@ static function (): void {
34763496 $ hasYield = $ hasYield || $ result ->hasYield ();
34773497 $ throwPoints = array_merge ($ throwPoints , $ result ->getThrowPoints ());
34783498 $ impurePoints = array_merge ($ impurePoints , $ result ->getImpurePoints ());
3499+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ result ->isAlwaysTerminating ();
34793500 $ nonNullabilityResults [] = $ nonNullabilityResult ;
34803501 }
34813502 foreach (array_reverse ($ expr ->vars ) as $ var ) {
@@ -3490,6 +3511,7 @@ static function (): void {
34903511 $ hasYield = $ result ->hasYield ();
34913512 $ throwPoints = $ result ->getThrowPoints ();
34923513 $ impurePoints = $ result ->getImpurePoints ();
3514+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34933515 if ($ expr ->class instanceof Expr) {
34943516 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
34953517 $ scope = $ result ->getScope ();
@@ -3506,6 +3528,7 @@ static function (): void {
35063528 $ hasYield = false ;
35073529 $ throwPoints = [];
35083530 $ impurePoints = [];
3531+ $ isAlwaysTerminating = false ;
35093532 $ className = null ;
35103533 if ($ expr ->class instanceof Expr || $ expr ->class instanceof Name) {
35113534 if ($ expr ->class instanceof Expr) {
@@ -3630,6 +3653,7 @@ static function (): void {
36303653 $ hasYield = $ result ->hasYield ();
36313654 $ throwPoints = $ result ->getThrowPoints ();
36323655 $ impurePoints = $ result ->getImpurePoints ();
3656+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
36333657
36343658 $ newExpr = $ expr ;
36353659 if ($ expr instanceof Expr \PostInc) {
@@ -3658,20 +3682,23 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36583682 $ ternaryCondResult = $ this ->processExprNode ($ stmt , $ expr ->cond , $ scope , $ nodeCallback , $ context ->enterDeep ());
36593683 $ throwPoints = $ ternaryCondResult ->getThrowPoints ();
36603684 $ impurePoints = $ ternaryCondResult ->getImpurePoints ();
3685+ $ isAlwaysTerminating = $ ternaryCondResult ->isAlwaysTerminating ();
36613686 $ ifTrueScope = $ ternaryCondResult ->getTruthyScope ();
36623687 $ ifFalseScope = $ ternaryCondResult ->getFalseyScope ();
36633688 $ ifTrueType = null ;
36643689 if ($ expr ->if !== null ) {
36653690 $ ifResult = $ this ->processExprNode ($ stmt , $ expr ->if , $ ifTrueScope , $ nodeCallback , $ context );
36663691 $ throwPoints = array_merge ($ throwPoints , $ ifResult ->getThrowPoints ());
36673692 $ impurePoints = array_merge ($ impurePoints , $ ifResult ->getImpurePoints ());
3693+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ ifResult ->isAlwaysTerminating ();
36683694 $ ifTrueScope = $ ifResult ->getScope ();
36693695 $ ifTrueType = $ ifTrueScope ->getType ($ expr ->if );
36703696 }
36713697
36723698 $ elseResult = $ this ->processExprNode ($ stmt , $ expr ->else , $ ifFalseScope , $ nodeCallback , $ context );
36733699 $ throwPoints = array_merge ($ throwPoints , $ elseResult ->getThrowPoints ());
36743700 $ impurePoints = array_merge ($ impurePoints , $ elseResult ->getImpurePoints ());
3701+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ elseResult ->isAlwaysTerminating ();
36753702 $ ifFalseScope = $ elseResult ->getScope ();
36763703
36773704 $ condType = $ scope ->getType ($ expr ->cond );
@@ -3696,7 +3723,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36963723 return new ExpressionResult (
36973724 $ finalScope ,
36983725 $ ternaryCondResult ->hasYield (),
3699- false ,
3726+ $ isAlwaysTerminating ,
37003727 $ throwPoints ,
37013728 $ impurePoints ,
37023729 static fn (): MutatingScope => $ finalScope ->filterByTruthyValue ($ expr ),
@@ -3716,17 +3743,20 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37163743 true ,
37173744 ),
37183745 ];
3746+ $ isAlwaysTerminating = false ;
37193747 if ($ expr ->key !== null ) {
37203748 $ keyResult = $ this ->processExprNode ($ stmt , $ expr ->key , $ scope , $ nodeCallback , $ context ->enterDeep ());
37213749 $ scope = $ keyResult ->getScope ();
37223750 $ throwPoints = $ keyResult ->getThrowPoints ();
37233751 $ impurePoints = array_merge ($ impurePoints , $ keyResult ->getImpurePoints ());
3752+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ keyResult ->isAlwaysTerminating ();
37243753 }
37253754 if ($ expr ->value !== null ) {
37263755 $ valueResult = $ this ->processExprNode ($ stmt , $ expr ->value , $ scope , $ nodeCallback , $ context ->enterDeep ());
37273756 $ scope = $ valueResult ->getScope ();
37283757 $ throwPoints = array_merge ($ throwPoints , $ valueResult ->getThrowPoints ());
37293758 $ impurePoints = array_merge ($ impurePoints , $ valueResult ->getImpurePoints ());
3759+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ valueResult ->isAlwaysTerminating ();
37303760 }
37313761 $ hasYield = true ;
37323762 } elseif ($ expr instanceof Expr \Match_) {
@@ -3737,6 +3767,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37373767 $ hasYield = $ condResult ->hasYield ();
37383768 $ throwPoints = $ condResult ->getThrowPoints ();
37393769 $ impurePoints = $ condResult ->getImpurePoints ();
3770+ $ isAlwaysTerminating = $ condResult ->isAlwaysTerminating ();
37403771 $ matchScope = $ scope ->enterMatch ($ expr );
37413772 $ armNodes = [];
37423773 $ hasDefaultCond = false ;
@@ -3948,79 +3979,93 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
39483979 $ hasYield = $ result ->hasYield ();
39493980 $ throwPoints = $ result ->getThrowPoints ();
39503981 $ impurePoints = $ result ->getImpurePoints ();
3982+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39513983 $ scope = $ result ->getScope ();
39523984 } elseif ($ expr instanceof Expr \Throw_) {
39533985 $ hasYield = false ;
39543986 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39553987 $ throwPoints = $ result ->getThrowPoints ();
39563988 $ impurePoints = $ result ->getImpurePoints ();
3989+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39573990 $ throwPoints [] = ThrowPoint::createExplicit ($ scope , $ scope ->getType ($ expr ->expr ), $ expr , false );
39583991 } elseif ($ expr instanceof FunctionCallableNode) {
39593992 $ throwPoints = [];
39603993 $ impurePoints = [];
39613994 $ hasYield = false ;
3995+ $ isAlwaysTerminating = false ;
39623996 if ($ expr ->getName () instanceof Expr) {
39633997 $ result = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39643998 $ scope = $ result ->getScope ();
39653999 $ hasYield = $ result ->hasYield ();
39664000 $ throwPoints = $ result ->getThrowPoints ();
39674001 $ impurePoints = $ result ->getImpurePoints ();
4002+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39684003 }
39694004 } elseif ($ expr instanceof MethodCallableNode) {
39704005 $ result = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39714006 $ scope = $ result ->getScope ();
39724007 $ hasYield = $ result ->hasYield ();
39734008 $ throwPoints = $ result ->getThrowPoints ();
39744009 $ impurePoints = $ result ->getImpurePoints ();
4010+ $ isAlwaysTerminating = false ;
39754011 if ($ expr ->getName () instanceof Expr) {
39764012 $ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39774013 $ scope = $ nameResult ->getScope ();
39784014 $ hasYield = $ hasYield || $ nameResult ->hasYield ();
39794015 $ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
39804016 $ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4017+ $ isAlwaysTerminating = $ nameResult ->isAlwaysTerminating ();
39814018 }
39824019 } elseif ($ expr instanceof StaticMethodCallableNode) {
39834020 $ throwPoints = [];
39844021 $ impurePoints = [];
39854022 $ hasYield = false ;
4023+ $ isAlwaysTerminating = false ;
39864024 if ($ expr ->getClass () instanceof Expr) {
39874025 $ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39884026 $ scope = $ classResult ->getScope ();
39894027 $ hasYield = $ classResult ->hasYield ();
39904028 $ throwPoints = $ classResult ->getThrowPoints ();
39914029 $ impurePoints = $ classResult ->getImpurePoints ();
4030+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
39924031 }
39934032 if ($ expr ->getName () instanceof Expr) {
39944033 $ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39954034 $ scope = $ nameResult ->getScope ();
39964035 $ hasYield = $ hasYield || $ nameResult ->hasYield ();
39974036 $ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
39984037 $ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4038+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ nameResult ->isAlwaysTerminating ();
39994039 }
40004040 } elseif ($ expr instanceof InstantiationCallableNode) {
40014041 $ throwPoints = [];
40024042 $ impurePoints = [];
40034043 $ hasYield = false ;
4044+ $ isAlwaysTerminating = false ;
40044045 if ($ expr ->getClass () instanceof Expr) {
40054046 $ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
40064047 $ scope = $ classResult ->getScope ();
40074048 $ hasYield = $ classResult ->hasYield ();
40084049 $ throwPoints = $ classResult ->getThrowPoints ();
40094050 $ impurePoints = $ classResult ->getImpurePoints ();
4051+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
40104052 }
40114053 } elseif ($ expr instanceof Node \Scalar) {
40124054 $ hasYield = false ;
40134055 $ throwPoints = [];
40144056 $ impurePoints = [];
4057+ $ isAlwaysTerminating = false ;
40154058 } elseif ($ expr instanceof ConstFetch) {
40164059 $ hasYield = false ;
40174060 $ throwPoints = [];
40184061 $ impurePoints = [];
4062+ $ isAlwaysTerminating = false ;
40194063 $ nodeCallback ($ expr ->name , $ scope );
40204064 } else {
40214065 $ hasYield = false ;
40224066 $ throwPoints = [];
40234067 $ impurePoints = [];
4068+ $ isAlwaysTerminating = false ;
40244069 }
40254070
40264071 return new ExpressionResult (
0 commit comments