Skip to content

Commit 52b172e

Browse files
committed
Add $isAlwaysTerminating to every node-type if-branch
1 parent b7f8925 commit 52b172e

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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()) {
@@ -2941,6 +2942,7 @@ static function (): void {
29412942
$hasYield = false;
29422943
$throwPoints = [];
29432944
$impurePoints = [];
2945+
$isAlwaysTerminating = false;
29442946
if ($expr->class instanceof Expr) {
29452947
$objectClasses = $scope->getType($expr->class)->getObjectClassNames();
29462948
if (count($objectClasses) !== 1) {
@@ -3104,6 +3106,7 @@ static function (): void {
31043106
$hasYield = $result->hasYield();
31053107
$throwPoints = $result->getThrowPoints();
31063108
$impurePoints = $result->getImpurePoints();
3109+
$isAlwaysTerminating = false;
31073110
$scope = $result->getScope();
31083111
if ($expr->name instanceof Expr) {
31093112
$result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep());
@@ -3152,6 +3155,7 @@ static function (): void {
31523155
true,
31533156
),
31543157
];
3158+
$isAlwaysTerminating = false;
31553159
if ($expr->class instanceof Expr) {
31563160
$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep());
31573161
$hasYield = $result->hasYield();
@@ -3190,6 +3194,7 @@ static function (): void {
31903194
$hasYield = $result->hasYield();
31913195
$throwPoints = $result->getThrowPoints();
31923196
$impurePoints = $result->getImpurePoints();
3197+
$isAlwaysTerminating = false;
31933198
$scope = $result->getScope();
31943199
} elseif ($expr instanceof Exit_) {
31953200
$hasYield = false;
@@ -3211,6 +3216,7 @@ static function (): void {
32113216
$hasYield = false;
32123217
$throwPoints = [];
32133218
$impurePoints = [];
3219+
$isAlwaysTerminating = false;
32143220
foreach ($expr->parts as $part) {
32153221
if (!$part instanceof Expr) {
32163222
continue;
@@ -3226,6 +3232,7 @@ static function (): void {
32263232
$hasYield = false;
32273233
$throwPoints = [];
32283234
$impurePoints = [];
3235+
$isAlwaysTerminating = false;
32293236
if ($expr->dim !== null) {
32303237
$result = $this->processExprNode($stmt, $expr->dim, $scope, $nodeCallback, $context->enterDeep());
32313238
$hasYield = $result->hasYield();
@@ -3244,6 +3251,7 @@ static function (): void {
32443251
$hasYield = false;
32453252
$throwPoints = [];
32463253
$impurePoints = [];
3254+
$isAlwaysTerminating = false;
32473255
foreach ($expr->items as $arrayItem) {
32483256
$itemNodes[] = new LiteralArrayItem($scope, $arrayItem);
32493257
$nodeCallback($arrayItem, $scope);
@@ -3325,6 +3333,7 @@ static function (): void {
33253333
$hasYield = $condResult->hasYield() || $rightResult->hasYield();
33263334
$throwPoints = array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints());
33273335
$impurePoints = array_merge($condResult->getImpurePoints(), $rightResult->getImpurePoints());
3336+
$isAlwaysTerminating = false;
33283337
} elseif ($expr instanceof BinaryOp) {
33293338
$result = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep());
33303339
$scope = $result->getScope();
@@ -3357,11 +3366,13 @@ static function (): void {
33573366
true,
33583367
);
33593368
$hasYield = $result->hasYield();
3369+
$isAlwaysTerminating = false;
33603370
$scope = $result->getScope()->afterExtractCall();
33613371
} elseif ($expr instanceof Expr\Print_) {
33623372
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
33633373
$throwPoints = $result->getThrowPoints();
33643374
$impurePoints = $result->getImpurePoints();
3375+
$isAlwaysTerminating = $result->isAlwaysTerminating();
33653376
$impurePoints[] = new ImpurePoint($scope, $expr, 'print', 'print', true);
33663377
$hasYield = $result->hasYield();
33673378

@@ -3370,6 +3381,7 @@ static function (): void {
33703381
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
33713382
$throwPoints = $result->getThrowPoints();
33723383
$impurePoints = $result->getImpurePoints();
3384+
$isAlwaysTerminating = true;
33733385
$hasYield = $result->hasYield();
33743386

33753387
$exprType = $scope->getType($expr->expr);
@@ -3397,6 +3409,7 @@ static function (): void {
33973409
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
33983410
$throwPoints = $result->getThrowPoints();
33993411
$impurePoints = $result->getImpurePoints();
3412+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34003413
$hasYield = $result->hasYield();
34013414

34023415
$scope = $result->getScope();
@@ -3407,6 +3420,7 @@ static function (): void {
34073420
$impurePoints = $result->getImpurePoints();
34083421
$impurePoints[] = new ImpurePoint($scope, $expr, 'eval', 'eval', true);
34093422
$hasYield = $result->hasYield();
3423+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34103424

34113425
$scope = $result->getScope();
34123426
} elseif ($expr instanceof Expr\YieldFrom) {
@@ -3422,6 +3436,7 @@ static function (): void {
34223436
true,
34233437
);
34243438
$hasYield = true;
3439+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34253440

34263441
$scope = $result->getScope();
34273442
} elseif ($expr instanceof BooleanNot) {
@@ -3430,7 +3445,10 @@ static function (): void {
34303445
$hasYield = $result->hasYield();
34313446
$throwPoints = $result->getThrowPoints();
34323447
$impurePoints = $result->getImpurePoints();
3448+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34333449
} elseif ($expr instanceof Expr\ClassConstFetch) {
3450+
$isAlwaysTerminating = false;
3451+
34343452
if ($expr->class instanceof Expr) {
34353453
$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep());
34363454
$scope = $result->getScope();
@@ -3461,13 +3479,15 @@ static function (): void {
34613479
$hasYield = $result->hasYield();
34623480
$throwPoints = $result->getThrowPoints();
34633481
$impurePoints = $result->getImpurePoints();
3482+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34643483
$scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions());
34653484
$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr);
34663485
} elseif ($expr instanceof Expr\Isset_) {
34673486
$hasYield = false;
34683487
$throwPoints = [];
34693488
$impurePoints = [];
34703489
$nonNullabilityResults = [];
3490+
$isAlwaysTerminating = false;
34713491
foreach ($expr->vars as $var) {
34723492
$nonNullabilityResult = $this->ensureNonNullability($scope, $var);
34733493
$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var);
@@ -3476,6 +3496,7 @@ static function (): void {
34763496
$hasYield = $hasYield || $result->hasYield();
34773497
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
34783498
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
3499+
$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating();
34793500
$nonNullabilityResults[] = $nonNullabilityResult;
34803501
}
34813502
foreach (array_reverse($expr->vars) as $var) {
@@ -3490,6 +3511,7 @@ static function (): void {
34903511
$hasYield = $result->hasYield();
34913512
$throwPoints = $result->getThrowPoints();
34923513
$impurePoints = $result->getImpurePoints();
3514+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34933515
if ($expr->class instanceof Expr) {
34943516
$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep());
34953517
$scope = $result->getScope();
@@ -3506,6 +3528,7 @@ static function (): void {
35063528
$hasYield = false;
35073529
$throwPoints = [];
35083530
$impurePoints = [];
3531+
$isAlwaysTerminating = false;
35093532
$className = null;
35103533
if ($expr->class instanceof Expr || $expr->class instanceof Name) {
35113534
if ($expr->class instanceof Expr) {
@@ -3630,6 +3653,7 @@ static function (): void {
36303653
$hasYield = $result->hasYield();
36313654
$throwPoints = $result->getThrowPoints();
36323655
$impurePoints = $result->getImpurePoints();
3656+
$isAlwaysTerminating = $result->isAlwaysTerminating();
36333657

36343658
$newExpr = $expr;
36353659
if ($expr instanceof Expr\PostInc) {
@@ -3658,20 +3682,23 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36583682
$ternaryCondResult = $this->processExprNode($stmt, $expr->cond, $scope, $nodeCallback, $context->enterDeep());
36593683
$throwPoints = $ternaryCondResult->getThrowPoints();
36603684
$impurePoints = $ternaryCondResult->getImpurePoints();
3685+
$isAlwaysTerminating = $ternaryCondResult->isAlwaysTerminating();
36613686
$ifTrueScope = $ternaryCondResult->getTruthyScope();
36623687
$ifFalseScope = $ternaryCondResult->getFalseyScope();
36633688
$ifTrueType = null;
36643689
if ($expr->if !== null) {
36653690
$ifResult = $this->processExprNode($stmt, $expr->if, $ifTrueScope, $nodeCallback, $context);
36663691
$throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints());
36673692
$impurePoints = array_merge($impurePoints, $ifResult->getImpurePoints());
3693+
$isAlwaysTerminating = $isAlwaysTerminating || $ifResult->isAlwaysTerminating();
36683694
$ifTrueScope = $ifResult->getScope();
36693695
$ifTrueType = $ifTrueScope->getType($expr->if);
36703696
}
36713697

36723698
$elseResult = $this->processExprNode($stmt, $expr->else, $ifFalseScope, $nodeCallback, $context);
36733699
$throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints());
36743700
$impurePoints = array_merge($impurePoints, $elseResult->getImpurePoints());
3701+
$isAlwaysTerminating = $isAlwaysTerminating || $elseResult->isAlwaysTerminating();
36753702
$ifFalseScope = $elseResult->getScope();
36763703

36773704
$condType = $scope->getType($expr->cond);
@@ -3696,7 +3723,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36963723
return new ExpressionResult(
36973724
$finalScope,
36983725
$ternaryCondResult->hasYield(),
3699-
false,
3726+
$isAlwaysTerminating,
37003727
$throwPoints,
37013728
$impurePoints,
37023729
static fn (): MutatingScope => $finalScope->filterByTruthyValue($expr),
@@ -3716,17 +3743,20 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37163743
true,
37173744
),
37183745
];
3746+
$isAlwaysTerminating = false;
37193747
if ($expr->key !== null) {
37203748
$keyResult = $this->processExprNode($stmt, $expr->key, $scope, $nodeCallback, $context->enterDeep());
37213749
$scope = $keyResult->getScope();
37223750
$throwPoints = $keyResult->getThrowPoints();
37233751
$impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints());
3752+
$isAlwaysTerminating = $isAlwaysTerminating || $keyResult->isAlwaysTerminating();
37243753
}
37253754
if ($expr->value !== null) {
37263755
$valueResult = $this->processExprNode($stmt, $expr->value, $scope, $nodeCallback, $context->enterDeep());
37273756
$scope = $valueResult->getScope();
37283757
$throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints());
37293758
$impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints());
3759+
$isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating();
37303760
}
37313761
$hasYield = true;
37323762
} elseif ($expr instanceof Expr\Match_) {
@@ -3737,6 +3767,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37373767
$hasYield = $condResult->hasYield();
37383768
$throwPoints = $condResult->getThrowPoints();
37393769
$impurePoints = $condResult->getImpurePoints();
3770+
$isAlwaysTerminating = $condResult->isAlwaysTerminating();
37403771
$matchScope = $scope->enterMatch($expr);
37413772
$armNodes = [];
37423773
$hasDefaultCond = false;
@@ -3948,79 +3979,93 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
39483979
$hasYield = $result->hasYield();
39493980
$throwPoints = $result->getThrowPoints();
39503981
$impurePoints = $result->getImpurePoints();
3982+
$isAlwaysTerminating = $result->isAlwaysTerminating();
39513983
$scope = $result->getScope();
39523984
} elseif ($expr instanceof Expr\Throw_) {
39533985
$hasYield = false;
39543986
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, ExpressionContext::createDeep());
39553987
$throwPoints = $result->getThrowPoints();
39563988
$impurePoints = $result->getImpurePoints();
3989+
$isAlwaysTerminating = $result->isAlwaysTerminating();
39573990
$throwPoints[] = ThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false);
39583991
} elseif ($expr instanceof FunctionCallableNode) {
39593992
$throwPoints = [];
39603993
$impurePoints = [];
39613994
$hasYield = false;
3995+
$isAlwaysTerminating = false;
39623996
if ($expr->getName() instanceof Expr) {
39633997
$result = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep());
39643998
$scope = $result->getScope();
39653999
$hasYield = $result->hasYield();
39664000
$throwPoints = $result->getThrowPoints();
39674001
$impurePoints = $result->getImpurePoints();
4002+
$isAlwaysTerminating = $result->isAlwaysTerminating();
39684003
}
39694004
} elseif ($expr instanceof MethodCallableNode) {
39704005
$result = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep());
39714006
$scope = $result->getScope();
39724007
$hasYield = $result->hasYield();
39734008
$throwPoints = $result->getThrowPoints();
39744009
$impurePoints = $result->getImpurePoints();
4010+
$isAlwaysTerminating = false;
39754011
if ($expr->getName() instanceof Expr) {
39764012
$nameResult = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep());
39774013
$scope = $nameResult->getScope();
39784014
$hasYield = $hasYield || $nameResult->hasYield();
39794015
$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints());
39804016
$impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints());
4017+
$isAlwaysTerminating = $nameResult->isAlwaysTerminating();
39814018
}
39824019
} elseif ($expr instanceof StaticMethodCallableNode) {
39834020
$throwPoints = [];
39844021
$impurePoints = [];
39854022
$hasYield = false;
4023+
$isAlwaysTerminating = false;
39864024
if ($expr->getClass() instanceof Expr) {
39874025
$classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep());
39884026
$scope = $classResult->getScope();
39894027
$hasYield = $classResult->hasYield();
39904028
$throwPoints = $classResult->getThrowPoints();
39914029
$impurePoints = $classResult->getImpurePoints();
4030+
$isAlwaysTerminating = $isAlwaysTerminating || $classResult->isAlwaysTerminating();
39924031
}
39934032
if ($expr->getName() instanceof Expr) {
39944033
$nameResult = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep());
39954034
$scope = $nameResult->getScope();
39964035
$hasYield = $hasYield || $nameResult->hasYield();
39974036
$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints());
39984037
$impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints());
4038+
$isAlwaysTerminating = $isAlwaysTerminating || $nameResult->isAlwaysTerminating();
39994039
}
40004040
} elseif ($expr instanceof InstantiationCallableNode) {
40014041
$throwPoints = [];
40024042
$impurePoints = [];
40034043
$hasYield = false;
4044+
$isAlwaysTerminating = false;
40044045
if ($expr->getClass() instanceof Expr) {
40054046
$classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep());
40064047
$scope = $classResult->getScope();
40074048
$hasYield = $classResult->hasYield();
40084049
$throwPoints = $classResult->getThrowPoints();
40094050
$impurePoints = $classResult->getImpurePoints();
4051+
$isAlwaysTerminating = $isAlwaysTerminating || $classResult->isAlwaysTerminating();
40104052
}
40114053
} elseif ($expr instanceof Node\Scalar) {
40124054
$hasYield = false;
40134055
$throwPoints = [];
40144056
$impurePoints = [];
4057+
$isAlwaysTerminating = false;
40154058
} elseif ($expr instanceof ConstFetch) {
40164059
$hasYield = false;
40174060
$throwPoints = [];
40184061
$impurePoints = [];
4062+
$isAlwaysTerminating = false;
40194063
$nodeCallback($expr->name, $scope);
40204064
} else {
40214065
$hasYield = false;
40224066
$throwPoints = [];
40234067
$impurePoints = [];
4068+
$isAlwaysTerminating = false;
40244069
}
40254070

40264071
return new ExpressionResult(

tests/PHPStan/Analyser/ExpressionResultTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,50 @@ public static function dataIsAlwaysTerminating(): array
2626
'sprintf("hello %s", "abc");',
2727
false,
2828
],
29+
[
30+
'isset($x);',
31+
false,
32+
],
33+
[
34+
'$x ? "def" : "abc";',
35+
false,
36+
],
2937
[
3038
'sprintf("hello %s", exit());',
3139
true,
3240
],
41+
[
42+
'(string) exit();',
43+
true,
44+
],
45+
[
46+
'!exit();',
47+
true,
48+
],
49+
[
50+
'eval(exit());',
51+
true,
52+
],
53+
[
54+
'empty(exit());',
55+
true,
56+
],
57+
[
58+
'isset(exit());',
59+
true,
60+
],
61+
[
62+
'$x ? "abc" : exit();',
63+
true,
64+
],
65+
[
66+
'$x ? exit() : "abc";',
67+
true,
68+
],
69+
[
70+
'fn() => yield (exit());',
71+
true,
72+
],
3373
];
3474
}
3575

0 commit comments

Comments
 (0)