@@ -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 ()) {
@@ -2940,6 +2941,7 @@ static function (): void {
29402941 $ hasYield = false ;
29412942 $ throwPoints = [];
29422943 $ impurePoints = [];
2944+ $ isAlwaysTerminating = false ;
29432945 if ($ expr ->class instanceof Expr) {
29442946 $ objectClasses = $ scope ->getType ($ expr ->class )->getObjectClassNames ();
29452947 if (count ($ objectClasses ) !== 1 ) {
@@ -3103,6 +3105,7 @@ static function (): void {
31033105 $ hasYield = $ result ->hasYield ();
31043106 $ throwPoints = $ result ->getThrowPoints ();
31053107 $ impurePoints = $ result ->getImpurePoints ();
3108+ $ isAlwaysTerminating = false ;
31063109 $ scope = $ result ->getScope ();
31073110 if ($ expr ->name instanceof Expr) {
31083111 $ result = $ this ->processExprNode ($ stmt , $ expr ->name , $ scope , $ nodeCallback , $ context ->enterDeep ());
@@ -3151,6 +3154,7 @@ static function (): void {
31513154 true ,
31523155 ),
31533156 ];
3157+ $ isAlwaysTerminating = false ;
31543158 if ($ expr ->class instanceof Expr) {
31553159 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
31563160 $ hasYield = $ result ->hasYield ();
@@ -3189,6 +3193,7 @@ static function (): void {
31893193 $ hasYield = $ result ->hasYield ();
31903194 $ throwPoints = $ result ->getThrowPoints ();
31913195 $ impurePoints = $ result ->getImpurePoints ();
3196+ $ isAlwaysTerminating = false ;
31923197 $ scope = $ result ->getScope ();
31933198 } elseif ($ expr instanceof Exit_) {
31943199 $ hasYield = false ;
@@ -3210,6 +3215,7 @@ static function (): void {
32103215 $ hasYield = false ;
32113216 $ throwPoints = [];
32123217 $ impurePoints = [];
3218+ $ isAlwaysTerminating = false ;
32133219 foreach ($ expr ->parts as $ part ) {
32143220 if (!$ part instanceof Expr) {
32153221 continue ;
@@ -3225,6 +3231,7 @@ static function (): void {
32253231 $ hasYield = false ;
32263232 $ throwPoints = [];
32273233 $ impurePoints = [];
3234+ $ isAlwaysTerminating = false ;
32283235 if ($ expr ->dim !== null ) {
32293236 $ result = $ this ->processExprNode ($ stmt , $ expr ->dim , $ scope , $ nodeCallback , $ context ->enterDeep ());
32303237 $ hasYield = $ result ->hasYield ();
@@ -3243,6 +3250,7 @@ static function (): void {
32433250 $ hasYield = false ;
32443251 $ throwPoints = [];
32453252 $ impurePoints = [];
3253+ $ isAlwaysTerminating = false ;
32463254 foreach ($ expr ->items as $ arrayItem ) {
32473255 $ itemNodes [] = new LiteralArrayItem ($ scope , $ arrayItem );
32483256 $ nodeCallback ($ arrayItem , $ scope );
@@ -3324,6 +3332,7 @@ static function (): void {
33243332 $ hasYield = $ condResult ->hasYield () || $ rightResult ->hasYield ();
33253333 $ throwPoints = array_merge ($ condResult ->getThrowPoints (), $ rightResult ->getThrowPoints ());
33263334 $ impurePoints = array_merge ($ condResult ->getImpurePoints (), $ rightResult ->getImpurePoints ());
3335+ $ isAlwaysTerminating = false ;
33273336 } elseif ($ expr instanceof BinaryOp) {
33283337 $ result = $ this ->processExprNode ($ stmt , $ expr ->left , $ scope , $ nodeCallback , $ context ->enterDeep ());
33293338 $ scope = $ result ->getScope ();
@@ -3356,11 +3365,13 @@ static function (): void {
33563365 true ,
33573366 );
33583367 $ hasYield = $ result ->hasYield ();
3368+ $ isAlwaysTerminating = false ;
33593369 $ scope = $ result ->getScope ()->afterExtractCall ();
33603370 } elseif ($ expr instanceof Expr \Print_) {
33613371 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33623372 $ throwPoints = $ result ->getThrowPoints ();
33633373 $ impurePoints = $ result ->getImpurePoints ();
3374+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
33643375 $ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'print ' , 'print ' , true );
33653376 $ hasYield = $ result ->hasYield ();
33663377
@@ -3369,6 +3380,7 @@ static function (): void {
33693380 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33703381 $ throwPoints = $ result ->getThrowPoints ();
33713382 $ impurePoints = $ result ->getImpurePoints ();
3383+ $ isAlwaysTerminating = true ;
33723384 $ hasYield = $ result ->hasYield ();
33733385
33743386 $ exprType = $ scope ->getType ($ expr ->expr );
@@ -3396,6 +3408,7 @@ static function (): void {
33963408 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33973409 $ throwPoints = $ result ->getThrowPoints ();
33983410 $ impurePoints = $ result ->getImpurePoints ();
3411+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
33993412 $ hasYield = $ result ->hasYield ();
34003413
34013414 $ scope = $ result ->getScope ();
@@ -3406,6 +3419,7 @@ static function (): void {
34063419 $ impurePoints = $ result ->getImpurePoints ();
34073420 $ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'eval ' , 'eval ' , true );
34083421 $ hasYield = $ result ->hasYield ();
3422+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34093423
34103424 $ scope = $ result ->getScope ();
34113425 } elseif ($ expr instanceof Expr \YieldFrom) {
@@ -3421,6 +3435,7 @@ static function (): void {
34213435 true ,
34223436 );
34233437 $ hasYield = true ;
3438+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34243439
34253440 $ scope = $ result ->getScope ();
34263441 } elseif ($ expr instanceof BooleanNot) {
@@ -3429,7 +3444,10 @@ static function (): void {
34293444 $ hasYield = $ result ->hasYield ();
34303445 $ throwPoints = $ result ->getThrowPoints ();
34313446 $ impurePoints = $ result ->getImpurePoints ();
3447+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34323448 } elseif ($ expr instanceof Expr \ClassConstFetch) {
3449+ $ isAlwaysTerminating = false ;
3450+
34333451 if ($ expr ->class instanceof Expr) {
34343452 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
34353453 $ scope = $ result ->getScope ();
@@ -3460,13 +3478,15 @@ static function (): void {
34603478 $ hasYield = $ result ->hasYield ();
34613479 $ throwPoints = $ result ->getThrowPoints ();
34623480 $ impurePoints = $ result ->getImpurePoints ();
3481+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34633482 $ scope = $ this ->revertNonNullability ($ scope , $ nonNullabilityResult ->getSpecifiedExpressions ());
34643483 $ scope = $ this ->lookForUnsetAllowedUndefinedExpressions ($ scope , $ expr ->expr );
34653484 } elseif ($ expr instanceof Expr \Isset_) {
34663485 $ hasYield = false ;
34673486 $ throwPoints = [];
34683487 $ impurePoints = [];
34693488 $ nonNullabilityResults = [];
3489+ $ isAlwaysTerminating = false ;
34703490 foreach ($ expr ->vars as $ var ) {
34713491 $ nonNullabilityResult = $ this ->ensureNonNullability ($ scope , $ var );
34723492 $ scope = $ this ->lookForSetAllowedUndefinedExpressions ($ nonNullabilityResult ->getScope (), $ var );
@@ -3475,6 +3495,7 @@ static function (): void {
34753495 $ hasYield = $ hasYield || $ result ->hasYield ();
34763496 $ throwPoints = array_merge ($ throwPoints , $ result ->getThrowPoints ());
34773497 $ impurePoints = array_merge ($ impurePoints , $ result ->getImpurePoints ());
3498+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ result ->isAlwaysTerminating ();
34783499 $ nonNullabilityResults [] = $ nonNullabilityResult ;
34793500 }
34803501 foreach (array_reverse ($ expr ->vars ) as $ var ) {
@@ -3489,6 +3510,7 @@ static function (): void {
34893510 $ hasYield = $ result ->hasYield ();
34903511 $ throwPoints = $ result ->getThrowPoints ();
34913512 $ impurePoints = $ result ->getImpurePoints ();
3513+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34923514 if ($ expr ->class instanceof Expr) {
34933515 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
34943516 $ scope = $ result ->getScope ();
@@ -3505,6 +3527,7 @@ static function (): void {
35053527 $ hasYield = false ;
35063528 $ throwPoints = [];
35073529 $ impurePoints = [];
3530+ $ isAlwaysTerminating = false ;
35083531 $ className = null ;
35093532 if ($ expr ->class instanceof Expr || $ expr ->class instanceof Name) {
35103533 if ($ expr ->class instanceof Expr) {
@@ -3629,6 +3652,7 @@ static function (): void {
36293652 $ hasYield = $ result ->hasYield ();
36303653 $ throwPoints = $ result ->getThrowPoints ();
36313654 $ impurePoints = $ result ->getImpurePoints ();
3655+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
36323656
36333657 $ newExpr = $ expr ;
36343658 if ($ expr instanceof Expr \PostInc) {
@@ -3657,20 +3681,23 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36573681 $ ternaryCondResult = $ this ->processExprNode ($ stmt , $ expr ->cond , $ scope , $ nodeCallback , $ context ->enterDeep ());
36583682 $ throwPoints = $ ternaryCondResult ->getThrowPoints ();
36593683 $ impurePoints = $ ternaryCondResult ->getImpurePoints ();
3684+ $ isAlwaysTerminating = $ ternaryCondResult ->isAlwaysTerminating ();
36603685 $ ifTrueScope = $ ternaryCondResult ->getTruthyScope ();
36613686 $ ifFalseScope = $ ternaryCondResult ->getFalseyScope ();
36623687 $ ifTrueType = null ;
36633688 if ($ expr ->if !== null ) {
36643689 $ ifResult = $ this ->processExprNode ($ stmt , $ expr ->if , $ ifTrueScope , $ nodeCallback , $ context );
36653690 $ throwPoints = array_merge ($ throwPoints , $ ifResult ->getThrowPoints ());
36663691 $ impurePoints = array_merge ($ impurePoints , $ ifResult ->getImpurePoints ());
3692+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ ifResult ->isAlwaysTerminating ();
36673693 $ ifTrueScope = $ ifResult ->getScope ();
36683694 $ ifTrueType = $ ifTrueScope ->getType ($ expr ->if );
36693695 }
36703696
36713697 $ elseResult = $ this ->processExprNode ($ stmt , $ expr ->else , $ ifFalseScope , $ nodeCallback , $ context );
36723698 $ throwPoints = array_merge ($ throwPoints , $ elseResult ->getThrowPoints ());
36733699 $ impurePoints = array_merge ($ impurePoints , $ elseResult ->getImpurePoints ());
3700+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ elseResult ->isAlwaysTerminating ();
36743701 $ ifFalseScope = $ elseResult ->getScope ();
36753702
36763703 $ condType = $ scope ->getType ($ expr ->cond );
@@ -3695,7 +3722,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36953722 return new ExpressionResult (
36963723 $ finalScope ,
36973724 $ ternaryCondResult ->hasYield (),
3698- false ,
3725+ $ isAlwaysTerminating ,
36993726 $ throwPoints ,
37003727 $ impurePoints ,
37013728 static fn (): MutatingScope => $ finalScope ->filterByTruthyValue ($ expr ),
@@ -3715,17 +3742,20 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37153742 true ,
37163743 ),
37173744 ];
3745+ $ isAlwaysTerminating = false ;
37183746 if ($ expr ->key !== null ) {
37193747 $ keyResult = $ this ->processExprNode ($ stmt , $ expr ->key , $ scope , $ nodeCallback , $ context ->enterDeep ());
37203748 $ scope = $ keyResult ->getScope ();
37213749 $ throwPoints = $ keyResult ->getThrowPoints ();
37223750 $ impurePoints = array_merge ($ impurePoints , $ keyResult ->getImpurePoints ());
3751+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ keyResult ->isAlwaysTerminating ();
37233752 }
37243753 if ($ expr ->value !== null ) {
37253754 $ valueResult = $ this ->processExprNode ($ stmt , $ expr ->value , $ scope , $ nodeCallback , $ context ->enterDeep ());
37263755 $ scope = $ valueResult ->getScope ();
37273756 $ throwPoints = array_merge ($ throwPoints , $ valueResult ->getThrowPoints ());
37283757 $ impurePoints = array_merge ($ impurePoints , $ valueResult ->getImpurePoints ());
3758+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ valueResult ->isAlwaysTerminating ();
37293759 }
37303760 $ hasYield = true ;
37313761 } elseif ($ expr instanceof Expr \Match_) {
@@ -3736,6 +3766,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37363766 $ hasYield = $ condResult ->hasYield ();
37373767 $ throwPoints = $ condResult ->getThrowPoints ();
37383768 $ impurePoints = $ condResult ->getImpurePoints ();
3769+ $ isAlwaysTerminating = $ condResult ->isAlwaysTerminating ();
37393770 $ matchScope = $ scope ->enterMatch ($ expr );
37403771 $ armNodes = [];
37413772 $ hasDefaultCond = false ;
@@ -3947,79 +3978,93 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
39473978 $ hasYield = $ result ->hasYield ();
39483979 $ throwPoints = $ result ->getThrowPoints ();
39493980 $ impurePoints = $ result ->getImpurePoints ();
3981+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39503982 $ scope = $ result ->getScope ();
39513983 } elseif ($ expr instanceof Expr \Throw_) {
39523984 $ hasYield = false ;
39533985 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39543986 $ throwPoints = $ result ->getThrowPoints ();
39553987 $ impurePoints = $ result ->getImpurePoints ();
3988+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39563989 $ throwPoints [] = ThrowPoint::createExplicit ($ scope , $ scope ->getType ($ expr ->expr ), $ expr , false );
39573990 } elseif ($ expr instanceof FunctionCallableNode) {
39583991 $ throwPoints = [];
39593992 $ impurePoints = [];
39603993 $ hasYield = false ;
3994+ $ isAlwaysTerminating = false ;
39613995 if ($ expr ->getName () instanceof Expr) {
39623996 $ result = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39633997 $ scope = $ result ->getScope ();
39643998 $ hasYield = $ result ->hasYield ();
39653999 $ throwPoints = $ result ->getThrowPoints ();
39664000 $ impurePoints = $ result ->getImpurePoints ();
4001+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39674002 }
39684003 } elseif ($ expr instanceof MethodCallableNode) {
39694004 $ result = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39704005 $ scope = $ result ->getScope ();
39714006 $ hasYield = $ result ->hasYield ();
39724007 $ throwPoints = $ result ->getThrowPoints ();
39734008 $ impurePoints = $ result ->getImpurePoints ();
4009+ $ isAlwaysTerminating = false ;
39744010 if ($ expr ->getName () instanceof Expr) {
39754011 $ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39764012 $ scope = $ nameResult ->getScope ();
39774013 $ hasYield = $ hasYield || $ nameResult ->hasYield ();
39784014 $ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
39794015 $ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4016+ $ isAlwaysTerminating = $ nameResult ->isAlwaysTerminating ();
39804017 }
39814018 } elseif ($ expr instanceof StaticMethodCallableNode) {
39824019 $ throwPoints = [];
39834020 $ impurePoints = [];
39844021 $ hasYield = false ;
4022+ $ isAlwaysTerminating = false ;
39854023 if ($ expr ->getClass () instanceof Expr) {
39864024 $ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39874025 $ scope = $ classResult ->getScope ();
39884026 $ hasYield = $ classResult ->hasYield ();
39894027 $ throwPoints = $ classResult ->getThrowPoints ();
39904028 $ impurePoints = $ classResult ->getImpurePoints ();
4029+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
39914030 }
39924031 if ($ expr ->getName () instanceof Expr) {
39934032 $ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39944033 $ scope = $ nameResult ->getScope ();
39954034 $ hasYield = $ hasYield || $ nameResult ->hasYield ();
39964035 $ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
39974036 $ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4037+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ nameResult ->isAlwaysTerminating ();
39984038 }
39994039 } elseif ($ expr instanceof InstantiationCallableNode) {
40004040 $ throwPoints = [];
40014041 $ impurePoints = [];
40024042 $ hasYield = false ;
4043+ $ isAlwaysTerminating = false ;
40034044 if ($ expr ->getClass () instanceof Expr) {
40044045 $ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
40054046 $ scope = $ classResult ->getScope ();
40064047 $ hasYield = $ classResult ->hasYield ();
40074048 $ throwPoints = $ classResult ->getThrowPoints ();
40084049 $ impurePoints = $ classResult ->getImpurePoints ();
4050+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
40094051 }
40104052 } elseif ($ expr instanceof Node \Scalar) {
40114053 $ hasYield = false ;
40124054 $ throwPoints = [];
40134055 $ impurePoints = [];
4056+ $ isAlwaysTerminating = false ;
40144057 } elseif ($ expr instanceof ConstFetch) {
40154058 $ hasYield = false ;
40164059 $ throwPoints = [];
40174060 $ impurePoints = [];
4061+ $ isAlwaysTerminating = false ;
40184062 $ nodeCallback ($ expr ->name , $ scope );
40194063 } else {
40204064 $ hasYield = false ;
40214065 $ throwPoints = [];
40224066 $ impurePoints = [];
4067+ $ isAlwaysTerminating = false ;
40234068 }
40244069
40254070 return new ExpressionResult (
0 commit comments