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