@@ -2415,13 +2415,13 @@ public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scop
2415
2415
return $ this ->processExprNode ($ stmt , $ newExpr , $ scope , $ nodeCallback , $ context );
2416
2416
}
2417
2417
2418
- $ isAlwaysTerminating = false ;
2419
2418
$ this ->callNodeCallbackWithExpression ($ nodeCallback , $ expr , $ scope , $ context );
2420
2419
2421
2420
if ($ expr instanceof Variable) {
2422
2421
$ hasYield = false ;
2423
2422
$ throwPoints = [];
2424
2423
$ impurePoints = [];
2424
+ $ isAlwaysTerminating = false ;
2425
2425
if ($ expr ->name instanceof Expr) {
2426
2426
return $ this ->processExprNode ($ stmt , $ expr ->name , $ scope , $ nodeCallback , $ context ->enterDeep ());
2427
2427
} elseif (in_array ($ expr ->name , Scope::SUPERGLOBAL_VARIABLES , true )) {
@@ -2539,6 +2539,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp
2539
2539
$ functionReflection = null ;
2540
2540
$ throwPoints = [];
2541
2541
$ impurePoints = [];
2542
+ $ isAlwaysTerminating = false ;
2542
2543
if ($ expr ->name instanceof Expr) {
2543
2544
$ nameType = $ scope ->getType ($ expr ->name );
2544
2545
if (!$ nameType ->isCallable ()->no ()) {
@@ -2943,6 +2944,7 @@ static function (): void {
2943
2944
$ hasYield = false ;
2944
2945
$ throwPoints = [];
2945
2946
$ impurePoints = [];
2947
+ $ isAlwaysTerminating = false ;
2946
2948
if ($ expr ->class instanceof Expr) {
2947
2949
$ objectClasses = $ scope ->getType ($ expr ->class )->getObjectClassNames ();
2948
2950
if (count ($ objectClasses ) !== 1 ) {
@@ -3106,6 +3108,7 @@ static function (): void {
3106
3108
$ hasYield = $ result ->hasYield ();
3107
3109
$ throwPoints = $ result ->getThrowPoints ();
3108
3110
$ impurePoints = $ result ->getImpurePoints ();
3111
+ $ isAlwaysTerminating = false ;
3109
3112
$ scope = $ result ->getScope ();
3110
3113
if ($ expr ->name instanceof Expr) {
3111
3114
$ result = $ this ->processExprNode ($ stmt , $ expr ->name , $ scope , $ nodeCallback , $ context ->enterDeep ());
@@ -3154,6 +3157,7 @@ static function (): void {
3154
3157
true ,
3155
3158
),
3156
3159
];
3160
+ $ isAlwaysTerminating = false ;
3157
3161
if ($ expr ->class instanceof Expr) {
3158
3162
$ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
3159
3163
$ hasYield = $ result ->hasYield ();
@@ -3192,6 +3196,7 @@ static function (): void {
3192
3196
$ hasYield = $ result ->hasYield ();
3193
3197
$ throwPoints = $ result ->getThrowPoints ();
3194
3198
$ impurePoints = $ result ->getImpurePoints ();
3199
+ $ isAlwaysTerminating = false ;
3195
3200
$ scope = $ result ->getScope ();
3196
3201
} elseif ($ expr instanceof Exit_) {
3197
3202
$ hasYield = false ;
@@ -3213,6 +3218,7 @@ static function (): void {
3213
3218
$ hasYield = false ;
3214
3219
$ throwPoints = [];
3215
3220
$ impurePoints = [];
3221
+ $ isAlwaysTerminating = false ;
3216
3222
foreach ($ expr ->parts as $ part ) {
3217
3223
if (!$ part instanceof Expr) {
3218
3224
continue ;
@@ -3228,6 +3234,7 @@ static function (): void {
3228
3234
$ hasYield = false ;
3229
3235
$ throwPoints = [];
3230
3236
$ impurePoints = [];
3237
+ $ isAlwaysTerminating = false ;
3231
3238
if ($ expr ->dim !== null ) {
3232
3239
$ result = $ this ->processExprNode ($ stmt , $ expr ->dim , $ scope , $ nodeCallback , $ context ->enterDeep ());
3233
3240
$ hasYield = $ result ->hasYield ();
@@ -3246,6 +3253,7 @@ static function (): void {
3246
3253
$ hasYield = false ;
3247
3254
$ throwPoints = [];
3248
3255
$ impurePoints = [];
3256
+ $ isAlwaysTerminating = false ;
3249
3257
foreach ($ expr ->items as $ arrayItem ) {
3250
3258
$ itemNodes [] = new LiteralArrayItem ($ scope , $ arrayItem );
3251
3259
$ nodeCallback ($ arrayItem , $ scope );
@@ -3327,6 +3335,7 @@ static function (): void {
3327
3335
$ hasYield = $ condResult ->hasYield () || $ rightResult ->hasYield ();
3328
3336
$ throwPoints = array_merge ($ condResult ->getThrowPoints (), $ rightResult ->getThrowPoints ());
3329
3337
$ impurePoints = array_merge ($ condResult ->getImpurePoints (), $ rightResult ->getImpurePoints ());
3338
+ $ isAlwaysTerminating = false ;
3330
3339
} elseif ($ expr instanceof BinaryOp) {
3331
3340
$ result = $ this ->processExprNode ($ stmt , $ expr ->left , $ scope , $ nodeCallback , $ context ->enterDeep ());
3332
3341
$ scope = $ result ->getScope ();
@@ -3359,11 +3368,13 @@ static function (): void {
3359
3368
true ,
3360
3369
);
3361
3370
$ hasYield = $ result ->hasYield ();
3371
+ $ isAlwaysTerminating = false ;
3362
3372
$ scope = $ result ->getScope ()->afterExtractCall ();
3363
3373
} elseif ($ expr instanceof Expr \Print_) {
3364
3374
$ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
3365
3375
$ throwPoints = $ result ->getThrowPoints ();
3366
3376
$ impurePoints = $ result ->getImpurePoints ();
3377
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3367
3378
$ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'print ' , 'print ' , true );
3368
3379
$ hasYield = $ result ->hasYield ();
3369
3380
@@ -3372,6 +3383,7 @@ static function (): void {
3372
3383
$ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
3373
3384
$ throwPoints = $ result ->getThrowPoints ();
3374
3385
$ impurePoints = $ result ->getImpurePoints ();
3386
+ $ isAlwaysTerminating = true ;
3375
3387
$ hasYield = $ result ->hasYield ();
3376
3388
3377
3389
$ exprType = $ scope ->getType ($ expr ->expr );
@@ -3399,6 +3411,7 @@ static function (): void {
3399
3411
$ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , $ context ->enterDeep ());
3400
3412
$ throwPoints = $ result ->getThrowPoints ();
3401
3413
$ impurePoints = $ result ->getImpurePoints ();
3414
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3402
3415
$ hasYield = $ result ->hasYield ();
3403
3416
3404
3417
$ scope = $ result ->getScope ();
@@ -3409,6 +3422,7 @@ static function (): void {
3409
3422
$ impurePoints = $ result ->getImpurePoints ();
3410
3423
$ impurePoints [] = new ImpurePoint ($ scope , $ expr , 'eval ' , 'eval ' , true );
3411
3424
$ hasYield = $ result ->hasYield ();
3425
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3412
3426
3413
3427
$ scope = $ result ->getScope ();
3414
3428
} elseif ($ expr instanceof Expr \YieldFrom) {
@@ -3424,6 +3438,7 @@ static function (): void {
3424
3438
true ,
3425
3439
);
3426
3440
$ hasYield = true ;
3441
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3427
3442
3428
3443
$ scope = $ result ->getScope ();
3429
3444
} elseif ($ expr instanceof BooleanNot) {
@@ -3432,7 +3447,10 @@ static function (): void {
3432
3447
$ hasYield = $ result ->hasYield ();
3433
3448
$ throwPoints = $ result ->getThrowPoints ();
3434
3449
$ impurePoints = $ result ->getImpurePoints ();
3450
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3435
3451
} elseif ($ expr instanceof Expr \ClassConstFetch) {
3452
+ $ isAlwaysTerminating = false ;
3453
+
3436
3454
if ($ expr ->class instanceof Expr) {
3437
3455
$ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
3438
3456
$ scope = $ result ->getScope ();
@@ -3463,13 +3481,15 @@ static function (): void {
3463
3481
$ hasYield = $ result ->hasYield ();
3464
3482
$ throwPoints = $ result ->getThrowPoints ();
3465
3483
$ impurePoints = $ result ->getImpurePoints ();
3484
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3466
3485
$ scope = $ this ->revertNonNullability ($ scope , $ nonNullabilityResult ->getSpecifiedExpressions ());
3467
3486
$ scope = $ this ->lookForUnsetAllowedUndefinedExpressions ($ scope , $ expr ->expr );
3468
3487
} elseif ($ expr instanceof Expr \Isset_) {
3469
3488
$ hasYield = false ;
3470
3489
$ throwPoints = [];
3471
3490
$ impurePoints = [];
3472
3491
$ nonNullabilityResults = [];
3492
+ $ isAlwaysTerminating = false ;
3473
3493
foreach ($ expr ->vars as $ var ) {
3474
3494
$ nonNullabilityResult = $ this ->ensureNonNullability ($ scope , $ var );
3475
3495
$ scope = $ this ->lookForSetAllowedUndefinedExpressions ($ nonNullabilityResult ->getScope (), $ var );
@@ -3478,6 +3498,7 @@ static function (): void {
3478
3498
$ hasYield = $ hasYield || $ result ->hasYield ();
3479
3499
$ throwPoints = array_merge ($ throwPoints , $ result ->getThrowPoints ());
3480
3500
$ impurePoints = array_merge ($ impurePoints , $ result ->getImpurePoints ());
3501
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ result ->isAlwaysTerminating ();
3481
3502
$ nonNullabilityResults [] = $ nonNullabilityResult ;
3482
3503
}
3483
3504
foreach (array_reverse ($ expr ->vars ) as $ var ) {
@@ -3492,6 +3513,7 @@ static function (): void {
3492
3513
$ hasYield = $ result ->hasYield ();
3493
3514
$ throwPoints = $ result ->getThrowPoints ();
3494
3515
$ impurePoints = $ result ->getImpurePoints ();
3516
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3495
3517
if ($ expr ->class instanceof Expr) {
3496
3518
$ result = $ this ->processExprNode ($ stmt , $ expr ->class , $ scope , $ nodeCallback , $ context ->enterDeep ());
3497
3519
$ scope = $ result ->getScope ();
@@ -3508,6 +3530,7 @@ static function (): void {
3508
3530
$ hasYield = false ;
3509
3531
$ throwPoints = [];
3510
3532
$ impurePoints = [];
3533
+ $ isAlwaysTerminating = false ;
3511
3534
$ className = null ;
3512
3535
if ($ expr ->class instanceof Expr || $ expr ->class instanceof Name) {
3513
3536
if ($ expr ->class instanceof Expr) {
@@ -3632,6 +3655,7 @@ static function (): void {
3632
3655
$ hasYield = $ result ->hasYield ();
3633
3656
$ throwPoints = $ result ->getThrowPoints ();
3634
3657
$ impurePoints = $ result ->getImpurePoints ();
3658
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3635
3659
3636
3660
$ newExpr = $ expr ;
3637
3661
if ($ expr instanceof Expr \PostInc) {
@@ -3660,20 +3684,23 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
3660
3684
$ ternaryCondResult = $ this ->processExprNode ($ stmt , $ expr ->cond , $ scope , $ nodeCallback , $ context ->enterDeep ());
3661
3685
$ throwPoints = $ ternaryCondResult ->getThrowPoints ();
3662
3686
$ impurePoints = $ ternaryCondResult ->getImpurePoints ();
3687
+ $ isAlwaysTerminating = $ ternaryCondResult ->isAlwaysTerminating ();
3663
3688
$ ifTrueScope = $ ternaryCondResult ->getTruthyScope ();
3664
3689
$ ifFalseScope = $ ternaryCondResult ->getFalseyScope ();
3665
3690
$ ifTrueType = null ;
3666
3691
if ($ expr ->if !== null ) {
3667
3692
$ ifResult = $ this ->processExprNode ($ stmt , $ expr ->if , $ ifTrueScope , $ nodeCallback , $ context );
3668
3693
$ throwPoints = array_merge ($ throwPoints , $ ifResult ->getThrowPoints ());
3669
3694
$ impurePoints = array_merge ($ impurePoints , $ ifResult ->getImpurePoints ());
3695
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ ifResult ->isAlwaysTerminating ();
3670
3696
$ ifTrueScope = $ ifResult ->getScope ();
3671
3697
$ ifTrueType = $ ifTrueScope ->getType ($ expr ->if );
3672
3698
}
3673
3699
3674
3700
$ elseResult = $ this ->processExprNode ($ stmt , $ expr ->else , $ ifFalseScope , $ nodeCallback , $ context );
3675
3701
$ throwPoints = array_merge ($ throwPoints , $ elseResult ->getThrowPoints ());
3676
3702
$ impurePoints = array_merge ($ impurePoints , $ elseResult ->getImpurePoints ());
3703
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ elseResult ->isAlwaysTerminating ();
3677
3704
$ ifFalseScope = $ elseResult ->getScope ();
3678
3705
3679
3706
$ condType = $ scope ->getType ($ expr ->cond );
@@ -3698,7 +3725,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
3698
3725
return new ExpressionResult (
3699
3726
$ finalScope ,
3700
3727
$ ternaryCondResult ->hasYield (),
3701
- false ,
3728
+ $ isAlwaysTerminating ,
3702
3729
$ throwPoints ,
3703
3730
$ impurePoints ,
3704
3731
static fn (): MutatingScope => $ finalScope ->filterByTruthyValue ($ expr ),
@@ -3718,17 +3745,20 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
3718
3745
true ,
3719
3746
),
3720
3747
];
3748
+ $ isAlwaysTerminating = false ;
3721
3749
if ($ expr ->key !== null ) {
3722
3750
$ keyResult = $ this ->processExprNode ($ stmt , $ expr ->key , $ scope , $ nodeCallback , $ context ->enterDeep ());
3723
3751
$ scope = $ keyResult ->getScope ();
3724
3752
$ throwPoints = $ keyResult ->getThrowPoints ();
3725
3753
$ impurePoints = array_merge ($ impurePoints , $ keyResult ->getImpurePoints ());
3754
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ keyResult ->isAlwaysTerminating ();
3726
3755
}
3727
3756
if ($ expr ->value !== null ) {
3728
3757
$ valueResult = $ this ->processExprNode ($ stmt , $ expr ->value , $ scope , $ nodeCallback , $ context ->enterDeep ());
3729
3758
$ scope = $ valueResult ->getScope ();
3730
3759
$ throwPoints = array_merge ($ throwPoints , $ valueResult ->getThrowPoints ());
3731
3760
$ impurePoints = array_merge ($ impurePoints , $ valueResult ->getImpurePoints ());
3761
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ valueResult ->isAlwaysTerminating ();
3732
3762
}
3733
3763
$ hasYield = true ;
3734
3764
} elseif ($ expr instanceof Expr \Match_) {
@@ -3739,6 +3769,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
3739
3769
$ hasYield = $ condResult ->hasYield ();
3740
3770
$ throwPoints = $ condResult ->getThrowPoints ();
3741
3771
$ impurePoints = $ condResult ->getImpurePoints ();
3772
+ $ isAlwaysTerminating = $ condResult ->isAlwaysTerminating ();
3742
3773
$ matchScope = $ scope ->enterMatch ($ expr );
3743
3774
$ armNodes = [];
3744
3775
$ hasDefaultCond = false ;
@@ -3950,79 +3981,93 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
3950
3981
$ hasYield = $ result ->hasYield ();
3951
3982
$ throwPoints = $ result ->getThrowPoints ();
3952
3983
$ impurePoints = $ result ->getImpurePoints ();
3984
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3953
3985
$ scope = $ result ->getScope ();
3954
3986
} elseif ($ expr instanceof Expr \Throw_) {
3955
3987
$ hasYield = false ;
3956
3988
$ result = $ this ->processExprNode ($ stmt , $ expr ->expr , $ scope , $ nodeCallback , ExpressionContext::createDeep ());
3957
3989
$ throwPoints = $ result ->getThrowPoints ();
3958
3990
$ impurePoints = $ result ->getImpurePoints ();
3991
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3959
3992
$ throwPoints [] = ThrowPoint::createExplicit ($ scope , $ scope ->getType ($ expr ->expr ), $ expr , false );
3960
3993
} elseif ($ expr instanceof FunctionCallableNode) {
3961
3994
$ throwPoints = [];
3962
3995
$ impurePoints = [];
3963
3996
$ hasYield = false ;
3997
+ $ isAlwaysTerminating = false ;
3964
3998
if ($ expr ->getName () instanceof Expr) {
3965
3999
$ result = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
3966
4000
$ scope = $ result ->getScope ();
3967
4001
$ hasYield = $ result ->hasYield ();
3968
4002
$ throwPoints = $ result ->getThrowPoints ();
3969
4003
$ impurePoints = $ result ->getImpurePoints ();
4004
+ $ isAlwaysTerminating = $ result ->isAlwaysTerminating ();
3970
4005
}
3971
4006
} elseif ($ expr instanceof MethodCallableNode) {
3972
4007
$ result = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
3973
4008
$ scope = $ result ->getScope ();
3974
4009
$ hasYield = $ result ->hasYield ();
3975
4010
$ throwPoints = $ result ->getThrowPoints ();
3976
4011
$ impurePoints = $ result ->getImpurePoints ();
4012
+ $ isAlwaysTerminating = false ;
3977
4013
if ($ expr ->getName () instanceof Expr) {
3978
4014
$ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getVar (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
3979
4015
$ scope = $ nameResult ->getScope ();
3980
4016
$ hasYield = $ hasYield || $ nameResult ->hasYield ();
3981
4017
$ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
3982
4018
$ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4019
+ $ isAlwaysTerminating = $ nameResult ->isAlwaysTerminating ();
3983
4020
}
3984
4021
} elseif ($ expr instanceof StaticMethodCallableNode) {
3985
4022
$ throwPoints = [];
3986
4023
$ impurePoints = [];
3987
4024
$ hasYield = false ;
4025
+ $ isAlwaysTerminating = false ;
3988
4026
if ($ expr ->getClass () instanceof Expr) {
3989
4027
$ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
3990
4028
$ scope = $ classResult ->getScope ();
3991
4029
$ hasYield = $ classResult ->hasYield ();
3992
4030
$ throwPoints = $ classResult ->getThrowPoints ();
3993
4031
$ impurePoints = $ classResult ->getImpurePoints ();
4032
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
3994
4033
}
3995
4034
if ($ expr ->getName () instanceof Expr) {
3996
4035
$ nameResult = $ this ->processExprNode ($ stmt , $ expr ->getName (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
3997
4036
$ scope = $ nameResult ->getScope ();
3998
4037
$ hasYield = $ hasYield || $ nameResult ->hasYield ();
3999
4038
$ throwPoints = array_merge ($ throwPoints , $ nameResult ->getThrowPoints ());
4000
4039
$ impurePoints = array_merge ($ impurePoints , $ nameResult ->getImpurePoints ());
4040
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ nameResult ->isAlwaysTerminating ();
4001
4041
}
4002
4042
} elseif ($ expr instanceof InstantiationCallableNode) {
4003
4043
$ throwPoints = [];
4004
4044
$ impurePoints = [];
4005
4045
$ hasYield = false ;
4046
+ $ isAlwaysTerminating = false ;
4006
4047
if ($ expr ->getClass () instanceof Expr) {
4007
4048
$ classResult = $ this ->processExprNode ($ stmt , $ expr ->getClass (), $ scope , $ nodeCallback , ExpressionContext::createDeep ());
4008
4049
$ scope = $ classResult ->getScope ();
4009
4050
$ hasYield = $ classResult ->hasYield ();
4010
4051
$ throwPoints = $ classResult ->getThrowPoints ();
4011
4052
$ impurePoints = $ classResult ->getImpurePoints ();
4053
+ $ isAlwaysTerminating = $ isAlwaysTerminating || $ classResult ->isAlwaysTerminating ();
4012
4054
}
4013
4055
} elseif ($ expr instanceof Node \Scalar) {
4014
4056
$ hasYield = false ;
4015
4057
$ throwPoints = [];
4016
4058
$ impurePoints = [];
4059
+ $ isAlwaysTerminating = false ;
4017
4060
} elseif ($ expr instanceof ConstFetch) {
4018
4061
$ hasYield = false ;
4019
4062
$ throwPoints = [];
4020
4063
$ impurePoints = [];
4064
+ $ isAlwaysTerminating = false ;
4021
4065
$ nodeCallback ($ expr ->name , $ scope );
4022
4066
} else {
4023
4067
$ hasYield = false ;
4024
4068
$ throwPoints = [];
4025
4069
$ impurePoints = [];
4070
+ $ isAlwaysTerminating = false ;
4026
4071
}
4027
4072
4028
4073
return new ExpressionResult (
0 commit comments