Skip to content

Commit b718211

Browse files
committed
Add $isAlwaysTerminating to every node-type if-branch
1 parent 564a713 commit b718211

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
@@ -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(

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)