@@ -2415,13 +2415,13 @@ public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scop
24152415 return $ this ->processExprNode ($ stmt , $ newExpr , $ scope , $ nodeCallback , $ context );
24162416 }
24172417
2418- $ isAlwaysTerminating = false ;
24192418 $ this ->callNodeCallbackWithExpression ($ nodeCallback , $ expr , $ scope , $ context );
24202419
24212420 if ($ expr instanceof Variable) {
24222421 $ hasYield = false ;
24232422 $ throwPoints = [];
24242423 $ impurePoints = [];
2424+ $ isAlwaysTerminating = false ;
24252425 if ($ expr ->name instanceof Expr) {
24262426 return $ this ->processExprNode ($ stmt , $ expr ->name , $ scope , $ nodeCallback , $ context ->enterDeep ());
24272427 } elseif (in_array ($ expr ->name , Scope::SUPERGLOBAL_VARIABLES , true )) {
@@ -2539,6 +2539,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp
25392539 $ functionReflection = null ;
25402540 $ throwPoints = [];
25412541 $ impurePoints = [];
2542+ $ isAlwaysTerminating = false ;
25422543 if ($ expr ->name instanceof Expr) {
25432544 $ nameType = $ scope ->getType ($ expr ->name );
25442545 if (!$ nameType ->isCallable ()->no ()) {
@@ -2943,6 +2944,7 @@ static function (): void {
29432944 $ hasYield = false ;
29442945 $ throwPoints = [];
29452946 $ impurePoints = [];
2947+ $ isAlwaysTerminating = false ;
29462948 if ($ expr ->class instanceof Expr) {
29472949 $ objectClasses = $ scope ->getType ($ expr ->class )->getObjectClassNames ();
29482950 if (count ($ objectClasses ) !== 1 ) {
@@ -3106,6 +3108,7 @@ static function (): void {
31063108 $ hasYield = $ result ->hasYield ();
31073109 $ throwPoints = $ result ->getThrowPoints ();
31083110 $ impurePoints = $ result ->getImpurePoints ();
3111+ $ isAlwaysTerminating = false ;
31093112 $ scope = $ result ->getScope ();
31103113 if ($ expr ->name instanceof Expr) {
31113114 $ result = $ this ->processExprNode ($ stmt , $ expr ->name , $ scope , $ nodeCallback , $ context ->enterDeep ());
@@ -3154,6 +3157,7 @@ static function (): void {
31543157 true ,
31553158 ),
31563159 ];
3160+ $ isAlwaysTerminating = false ;
31573161 if ($ expr ->class instanceof Expr) {
31583162 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
31593163 $ hasYield = $ result ->hasYield ();
@@ -3192,6 +3196,7 @@ static function (): void {
31923196 $ hasYield = $ result ->hasYield ();
31933197 $ throwPoints = $ result ->getThrowPoints ();
31943198 $ impurePoints = $ result ->getImpurePoints ();
3199+ $ isAlwaysTerminating = false ;
31953200 $ scope = $ result ->getScope ();
31963201 } elseif ($ expr instanceof Exit_) {
31973202 $ hasYield = false ;
@@ -3213,6 +3218,7 @@ static function (): void {
32133218 $ hasYield = false ;
32143219 $ throwPoints = [];
32153220 $ impurePoints = [];
3221+ $ isAlwaysTerminating = false ;
32163222 foreach ($ expr ->parts as $ part ) {
32173223 if (!$ part instanceof Expr) {
32183224 continue ;
@@ -3228,6 +3234,7 @@ static function (): void {
32283234 $ hasYield = false ;
32293235 $ throwPoints = [];
32303236 $ impurePoints = [];
3237+ $ isAlwaysTerminating = false ;
32313238 if ($ expr ->dim !== null ) {
32323239 $ result = $ this ->processExprNode ($ stmt , $ expr ->dim , $ scope , $ nodeCallback , $ context ->enterDeep ());
32333240 $ hasYield = $ result ->hasYield ();
@@ -3246,6 +3253,7 @@ static function (): void {
32463253 $ hasYield = false ;
32473254 $ throwPoints = [];
32483255 $ impurePoints = [];
3256+ $ isAlwaysTerminating = false ;
32493257 foreach ($ expr ->items as $ arrayItem ) {
32503258 $ itemNodes [] = new LiteralArrayItem ($ scope , $ arrayItem );
32513259 $ nodeCallback ($ arrayItem , $ scope );
@@ -3327,6 +3335,7 @@ static function (): void {
33273335 $ hasYield = $ condResult ->hasYield () || $ rightResult ->hasYield ();
33283336 $ throwPoints = array_merge ($ condResult ->getThrowPoints (), $ rightResult ->getThrowPoints ());
33293337 $ impurePoints = array_merge ($ condResult ->getImpurePoints (), $ rightResult ->getImpurePoints ());
3338+ $ isAlwaysTerminating = false ;
33303339 } elseif ($ expr instanceof BinaryOp) {
33313340 $ result = $ this ->processExprNode ($ stmt , $ expr ->left , $ scope , $ nodeCallback , $ context ->enterDeep ());
33323341 $ scope = $ result ->getScope ();
@@ -3359,11 +3368,13 @@ static function (): void {
33593368 true ,
33603369 );
33613370 $ hasYield = $ result ->hasYield ();
3371+ $ isAlwaysTerminating = false ;
33623372 $ scope = $ result ->getScope ()->afterExtractCall ();
33633373 } elseif ($ expr instanceof Expr \Print_) {
33643374 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33653375 $ throwPoints = $ result ->getThrowPoints ();
33663376 $ impurePoints = $ result ->getImpurePoints ();
3377+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
33673378 $ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'print ' , 'print ' , true );
33683379 $ hasYield = $ result ->hasYield ();
33693380
@@ -3372,6 +3383,7 @@ static function (): void {
33723383 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
33733384 $ throwPoints = $ result ->getThrowPoints ();
33743385 $ impurePoints = $ result ->getImpurePoints ();
3386+ $ isAlwaysTerminating = true ;
33753387 $ hasYield = $ result ->hasYield ();
33763388
33773389 $ exprType = $ scope ->getType ($ expr ->expr );
@@ -3399,6 +3411,7 @@ static function (): void {
33993411 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
34003412 $ throwPoints = $ result ->getThrowPoints ();
34013413 $ impurePoints = $ result ->getImpurePoints ();
3414+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34023415 $ hasYield = $ result ->hasYield ();
34033416
34043417 $ scope = $ result ->getScope ();
@@ -3409,6 +3422,7 @@ static function (): void {
34093422 $ impurePoints = $ result ->getImpurePoints ();
34103423 $ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'eval ' , 'eval ' , true );
34113424 $ hasYield = $ result ->hasYield ();
3425+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34123426
34133427 $ scope = $ result ->getScope ();
34143428 } elseif ($ expr instanceof Expr \YieldFrom) {
@@ -3424,6 +3438,7 @@ static function (): void {
34243438 true ,
34253439 );
34263440 $ hasYield = true ;
3441+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34273442
34283443 $ scope = $ result ->getScope ();
34293444 } elseif ($ expr instanceof BooleanNot) {
@@ -3432,7 +3447,10 @@ static function (): void {
34323447 $ hasYield = $ result ->hasYield ();
34333448 $ throwPoints = $ result ->getThrowPoints ();
34343449 $ impurePoints = $ result ->getImpurePoints ();
3450+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34353451 } elseif ($ expr instanceof Expr \ClassConstFetch) {
3452+ $ isAlwaysTerminating = false ;
3453+
34363454 if ($ expr ->class instanceof Expr) {
34373455 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
34383456 $ scope = $ result ->getScope ();
@@ -3463,13 +3481,15 @@ static function (): void {
34633481 $ hasYield = $ result ->hasYield ();
34643482 $ throwPoints = $ result ->getThrowPoints ();
34653483 $ impurePoints = $ result ->getImpurePoints ();
3484+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34663485 $ scope = $ this ->revertNonNullability ($ scope , $ nonNullabilityResult ->getSpecifiedExpressions ());
34673486 $ scope = $ this ->lookForUnsetAllowedUndefinedExpressions ($ scope , $ expr ->expr );
34683487 } elseif ($ expr instanceof Expr \Isset_) {
34693488 $ hasYield = false ;
34703489 $ throwPoints = [];
34713490 $ impurePoints = [];
34723491 $ nonNullabilityResults = [];
3492+ $ isAlwaysTerminating = false ;
34733493 foreach ($ expr ->vars as $ var ) {
34743494 $ nonNullabilityResult = $ this ->ensureNonNullability ($ scope , $ var );
34753495 $ scope = $ this ->lookForSetAllowedUndefinedExpressions ($ nonNullabilityResult ->getScope (), $ var );
@@ -3478,6 +3498,7 @@ static function (): void {
34783498 $ hasYield = $ hasYield || $ result ->hasYield ();
34793499 $ throwPoints = array_merge ($ throwPoints , $ result ->getThrowPoints ());
34803500 $ impurePoints = array_merge ($ impurePoints , $ result ->getImpurePoints ());
3501+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ result ->isAlwaysTerminating ();
34813502 $ nonNullabilityResults [] = $ nonNullabilityResult ;
34823503 }
34833504 foreach (array_reverse ($ expr ->vars ) as $ var ) {
@@ -3492,6 +3513,7 @@ static function (): void {
34923513 $ hasYield = $ result ->hasYield ();
34933514 $ throwPoints = $ result ->getThrowPoints ();
34943515 $ impurePoints = $ result ->getImpurePoints ();
3516+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
34953517 if ($ expr ->class instanceof Expr) {
34963518 $ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
34973519 $ scope = $ result ->getScope ();
@@ -3508,6 +3530,7 @@ static function (): void {
35083530 $ hasYield = false ;
35093531 $ throwPoints = [];
35103532 $ impurePoints = [];
3533+ $ isAlwaysTerminating = false ;
35113534 $ className = null ;
35123535 if ($ expr ->class instanceof Expr || $ expr ->class instanceof Name) {
35133536 if ($ expr ->class instanceof Expr) {
@@ -3632,6 +3655,7 @@ static function (): void {
36323655 $ hasYield = $ result ->hasYield ();
36333656 $ throwPoints = $ result ->getThrowPoints ();
36343657 $ impurePoints = $ result ->getImpurePoints ();
3658+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
36353659
36363660 $ newExpr = $ expr ;
36373661 if ($ expr instanceof Expr \PostInc) {
@@ -3660,20 +3684,23 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36603684 $ ternaryCondResult = $ this ->processExprNode ($ stmt , $ expr ->cond , $ scope , $ nodeCallback , $ context ->enterDeep ());
36613685 $ throwPoints = $ ternaryCondResult ->getThrowPoints ();
36623686 $ impurePoints = $ ternaryCondResult ->getImpurePoints ();
3687+ $ isAlwaysTerminating = $ ternaryCondResult ->isAlwaysTerminating ();
36633688 $ ifTrueScope = $ ternaryCondResult ->getTruthyScope ();
36643689 $ ifFalseScope = $ ternaryCondResult ->getFalseyScope ();
36653690 $ ifTrueType = null ;
36663691 if ($ expr ->if !== null ) {
36673692 $ ifResult = $ this ->processExprNode ($ stmt , $ expr ->if , $ ifTrueScope , $ nodeCallback , $ context );
36683693 $ throwPoints = array_merge ($ throwPoints , $ ifResult ->getThrowPoints ());
36693694 $ impurePoints = array_merge ($ impurePoints , $ ifResult ->getImpurePoints ());
3695+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ ifResult ->isAlwaysTerminating ();
36703696 $ ifTrueScope = $ ifResult ->getScope ();
36713697 $ ifTrueType = $ ifTrueScope ->getType ($ expr ->if );
36723698 }
36733699
36743700 $ elseResult = $ this ->processExprNode ($ stmt , $ expr ->else , $ ifFalseScope , $ nodeCallback , $ context );
36753701 $ throwPoints = array_merge ($ throwPoints , $ elseResult ->getThrowPoints ());
36763702 $ impurePoints = array_merge ($ impurePoints , $ elseResult ->getImpurePoints ());
3703+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ elseResult ->isAlwaysTerminating ();
36773704 $ ifFalseScope = $ elseResult ->getScope ();
36783705
36793706 $ condType = $ scope ->getType ($ expr ->cond );
@@ -3698,7 +3725,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36983725 return new ExpressionResult (
36993726 $ finalScope ,
37003727 $ ternaryCondResult ->hasYield (),
3701- false ,
3728+ $ isAlwaysTerminating ,
37023729 $ throwPoints ,
37033730 $ impurePoints ,
37043731 static fn (): MutatingScope => $ finalScope ->filterByTruthyValue ($ expr ),
@@ -3718,17 +3745,20 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37183745 true ,
37193746 ),
37203747 ];
3748+ $ isAlwaysTerminating = false ;
37213749 if ($ expr ->key !== null ) {
37223750 $ keyResult = $ this ->processExprNode ($ stmt , $ expr ->key , $ scope , $ nodeCallback , $ context ->enterDeep ());
37233751 $ scope = $ keyResult ->getScope ();
37243752 $ throwPoints = $ keyResult ->getThrowPoints ();
37253753 $ impurePoints = array_merge ($ impurePoints , $ keyResult ->getImpurePoints ());
3754+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ keyResult ->isAlwaysTerminating ();
37263755 }
37273756 if ($ expr ->value !== null ) {
37283757 $ valueResult = $ this ->processExprNode ($ stmt , $ expr ->value , $ scope , $ nodeCallback , $ context ->enterDeep ());
37293758 $ scope = $ valueResult ->getScope ();
37303759 $ throwPoints = array_merge ($ throwPoints , $ valueResult ->getThrowPoints ());
37313760 $ impurePoints = array_merge ($ impurePoints , $ valueResult ->getImpurePoints ());
3761+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ valueResult ->isAlwaysTerminating ();
37323762 }
37333763 $ hasYield = true ;
37343764 } elseif ($ expr instanceof Expr \Match_) {
@@ -3739,6 +3769,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37393769 $ hasYield = $ condResult ->hasYield ();
37403770 $ throwPoints = $ condResult ->getThrowPoints ();
37413771 $ impurePoints = $ condResult ->getImpurePoints ();
3772+ $ isAlwaysTerminating = $ condResult ->isAlwaysTerminating ();
37423773 $ matchScope = $ scope ->enterMatch ($ expr );
37433774 $ armNodes = [];
37443775 $ hasDefaultCond = false ;
@@ -3950,79 +3981,93 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
39503981 $ hasYield = $ result ->hasYield ();
39513982 $ throwPoints = $ result ->getThrowPoints ();
39523983 $ impurePoints = $ result ->getImpurePoints ();
3984+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39533985 $ scope = $ result ->getScope ();
39543986 } elseif ($ expr instanceof Expr \Throw_) {
39553987 $ hasYield = false ;
39563988 $ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39573989 $ throwPoints = $ result ->getThrowPoints ();
39583990 $ impurePoints = $ result ->getImpurePoints ();
3991+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39593992 $ throwPoints [] = ThrowPoint::createExplicit ($ scope , $ scope ->getType ($ expr ->expr ), $ expr , false );
39603993 } elseif ($ expr instanceof FunctionCallableNode) {
39613994 $ throwPoints = [];
39623995 $ impurePoints = [];
39633996 $ hasYield = false ;
3997+ $ isAlwaysTerminating = false ;
39643998 if ($ expr ->getName () instanceof Expr) {
39653999 $ result = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39664000 $ scope = $ result ->getScope ();
39674001 $ hasYield = $ result ->hasYield ();
39684002 $ throwPoints = $ result ->getThrowPoints ();
39694003 $ impurePoints = $ result ->getImpurePoints ();
4004+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
39704005 }
39714006 } elseif ($ expr instanceof MethodCallableNode) {
39724007 $ result = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39734008 $ scope = $ result ->getScope ();
39744009 $ hasYield = $ result ->hasYield ();
39754010 $ throwPoints = $ result ->getThrowPoints ();
39764011 $ impurePoints = $ result ->getImpurePoints ();
4012+ $ isAlwaysTerminating = false ;
39774013 if ($ expr ->getName () instanceof Expr) {
39784014 $ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39794015 $ scope = $ nameResult ->getScope ();
39804016 $ hasYield = $ hasYield || $ nameResult ->hasYield ();
39814017 $ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
39824018 $ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4019+ $ isAlwaysTerminating = $ nameResult ->isAlwaysTerminating ();
39834020 }
39844021 } elseif ($ expr instanceof StaticMethodCallableNode) {
39854022 $ throwPoints = [];
39864023 $ impurePoints = [];
39874024 $ hasYield = false ;
4025+ $ isAlwaysTerminating = false ;
39884026 if ($ expr ->getClass () instanceof Expr) {
39894027 $ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39904028 $ scope = $ classResult ->getScope ();
39914029 $ hasYield = $ classResult ->hasYield ();
39924030 $ throwPoints = $ classResult ->getThrowPoints ();
39934031 $ impurePoints = $ classResult ->getImpurePoints ();
4032+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
39944033 }
39954034 if ($ expr ->getName () instanceof Expr) {
39964035 $ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
39974036 $ scope = $ nameResult ->getScope ();
39984037 $ hasYield = $ hasYield || $ nameResult ->hasYield ();
39994038 $ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
40004039 $ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4040+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ nameResult ->isAlwaysTerminating ();
40014041 }
40024042 } elseif ($ expr instanceof InstantiationCallableNode) {
40034043 $ throwPoints = [];
40044044 $ impurePoints = [];
40054045 $ hasYield = false ;
4046+ $ isAlwaysTerminating = false ;
40064047 if ($ expr ->getClass () instanceof Expr) {
40074048 $ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
40084049 $ scope = $ classResult ->getScope ();
40094050 $ hasYield = $ classResult ->hasYield ();
40104051 $ throwPoints = $ classResult ->getThrowPoints ();
40114052 $ impurePoints = $ classResult ->getImpurePoints ();
4053+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
40124054 }
40134055 } elseif ($ expr instanceof Node \Scalar) {
40144056 $ hasYield = false ;
40154057 $ throwPoints = [];
40164058 $ impurePoints = [];
4059+ $ isAlwaysTerminating = false ;
40174060 } elseif ($ expr instanceof ConstFetch) {
40184061 $ hasYield = false ;
40194062 $ throwPoints = [];
40204063 $ impurePoints = [];
4064+ $ isAlwaysTerminating = false ;
40214065 $ nodeCallback ($ expr ->name , $ scope );
40224066 } else {
40234067 $ hasYield = false ;
40244068 $ throwPoints = [];
40254069 $ impurePoints = [];
4070+ $ isAlwaysTerminating = false ;
40264071 }
40274072
40284073 return new ExpressionResult (
0 commit comments