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