Skip to content

Commit b2a1743

Browse files
staabmondrejmirtes
authored andcommitted
Consider LastConditionVisitor in ConstantConditionRuleHelper::getBooleanType() using rules
1 parent 6f6e9d3 commit b2a1743

13 files changed

+179
-32
lines changed

Diff for: src/Rules/Comparison/BooleanAndConstantConditionRule.php

+13-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\BooleanAndNode;
8+
use PHPStan\Parser\LastConditionVisitor;
89
use PHPStan\Rules\Rule;
910
use PHPStan\Rules\RuleErrorBuilder;
1011
use PHPStan\Type\Constant\ConstantBooleanType;
@@ -53,19 +54,22 @@ public function processNode(
5354

5455
return $ruleErrorBuilder->tip($tipText);
5556
};
56-
$errors[] = $addTipLeft(RuleErrorBuilder::message(sprintf(
57-
'Left side of %s is always %s.',
58-
$nodeText,
59-
$leftType->getValue() ? 'true' : 'false',
60-
)))->line($originalNode->left->getLine())->build();
57+
58+
if ($leftType->getValue() === false || $originalNode->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) !== true) {
59+
$errors[] = $addTipLeft(RuleErrorBuilder::message(sprintf(
60+
'Left side of %s is always %s.',
61+
$nodeText,
62+
$leftType->getValue() ? 'true' : 'false',
63+
)))->line($originalNode->left->getLine())->build();
64+
}
6165
}
6266

6367
$rightScope = $node->getRightScope();
6468
$rightType = $this->helper->getBooleanType(
6569
$rightScope,
6670
$originalNode->right,
6771
);
68-
if ($rightType instanceof ConstantBooleanType) {
72+
if ($rightType instanceof ConstantBooleanType && !$scope->isInFirstLevelStatement()) {
6973
$addTipRight = function (RuleErrorBuilder $ruleErrorBuilder) use ($rightScope, $originalNode, $tipText): RuleErrorBuilder {
7074
if (!$this->treatPhpDocTypesAsCertain) {
7175
return $ruleErrorBuilder;
@@ -82,7 +86,7 @@ public function processNode(
8286
return $ruleErrorBuilder->tip($tipText);
8387
};
8488

85-
if (!$scope->isInFirstLevelStatement()) {
89+
if ($rightType->getValue() === false || $originalNode->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) !== true) {
8690
$errors[] = $addTipRight(RuleErrorBuilder::message(sprintf(
8791
'Right side of %s is always %s.',
8892
$nodeText,
@@ -91,7 +95,7 @@ public function processNode(
9195
}
9296
}
9397

94-
if (count($errors) === 0) {
98+
if (count($errors) === 0 && !$scope->isInFirstLevelStatement()) {
9599
$nodeType = $this->treatPhpDocTypesAsCertain ? $scope->getType($originalNode) : $scope->getNativeType($originalNode);
96100
if ($nodeType instanceof ConstantBooleanType) {
97101
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder {
@@ -107,7 +111,7 @@ public function processNode(
107111
return $ruleErrorBuilder->tip($tipText);
108112
};
109113

110-
if (!$scope->isInFirstLevelStatement()) {
114+
if ($nodeType->getValue() === false || $originalNode->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) !== true) {
111115
$errors[] = $addTip(RuleErrorBuilder::message(sprintf(
112116
'Result of %s is always %s.',
113117
$nodeText,

Diff for: src/Rules/Comparison/BooleanNotConstantConditionRule.php

+9-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Parser\LastConditionVisitor;
78
use PHPStan\Rules\Rule;
89
use PHPStan\Rules\RuleErrorBuilder;
910
use PHPStan\Type\Constant\ConstantBooleanType;
@@ -47,12 +48,14 @@ public function processNode(
4748
return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
4849
};
4950

50-
return [
51-
$addTip(RuleErrorBuilder::message(sprintf(
52-
'Negated boolean expression is always %s.',
53-
$exprType->getValue() ? 'false' : 'true',
54-
)))->line($node->expr->getLine())->build(),
55-
];
51+
if ($exprType->getValue() === true || $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) !== true) {
52+
return [
53+
$addTip(RuleErrorBuilder::message(sprintf(
54+
'Negated boolean expression is always %s.',
55+
$exprType->getValue() ? 'false' : 'true',
56+
)))->line($node->expr->getLine())->build(),
57+
];
58+
}
5659
}
5760

5861
return [];

Diff for: src/Rules/Comparison/BooleanOrConstantConditionRule.php

+13-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\BooleanOrNode;
8+
use PHPStan\Parser\LastConditionVisitor;
89
use PHPStan\Rules\Rule;
910
use PHPStan\Rules\RuleErrorBuilder;
1011
use PHPStan\Type\Constant\ConstantBooleanType;
@@ -53,19 +54,22 @@ public function processNode(
5354

5455
return $ruleErrorBuilder->tip($tipText);
5556
};
56-
$messages[] = $addTipLeft(RuleErrorBuilder::message(sprintf(
57-
'Left side of %s is always %s.',
58-
$nodeText,
59-
$leftType->getValue() ? 'true' : 'false',
60-
)))->line($originalNode->left->getLine())->build();
57+
58+
if ($leftType->getValue() === false || $originalNode->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) !== true) {
59+
$messages[] = $addTipLeft(RuleErrorBuilder::message(sprintf(
60+
'Left side of %s is always %s.',
61+
$nodeText,
62+
$leftType->getValue() ? 'true' : 'false',
63+
)))->line($originalNode->left->getLine())->build();
64+
}
6165
}
6266

6367
$rightScope = $node->getRightScope();
6468
$rightType = $this->helper->getBooleanType(
6569
$rightScope,
6670
$originalNode->right,
6771
);
68-
if ($rightType instanceof ConstantBooleanType) {
72+
if ($rightType instanceof ConstantBooleanType && !$scope->isInFirstLevelStatement()) {
6973
$addTipRight = function (RuleErrorBuilder $ruleErrorBuilder) use ($rightScope, $originalNode, $tipText): RuleErrorBuilder {
7074
if (!$this->treatPhpDocTypesAsCertain) {
7175
return $ruleErrorBuilder;
@@ -82,7 +86,7 @@ public function processNode(
8286
return $ruleErrorBuilder->tip($tipText);
8387
};
8488

85-
if (!$scope->isInFirstLevelStatement()) {
89+
if ($rightType->getValue() === false || $originalNode->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) !== true) {
8690
$messages[] = $addTipRight(RuleErrorBuilder::message(sprintf(
8791
'Right side of %s is always %s.',
8892
$nodeText,
@@ -91,7 +95,7 @@ public function processNode(
9195
}
9296
}
9397

94-
if (count($messages) === 0) {
98+
if (count($messages) === 0 && !$scope->isInFirstLevelStatement()) {
9599
$nodeType = $this->treatPhpDocTypesAsCertain ? $scope->getType($originalNode) : $scope->getNativeType($originalNode);
96100
if ($nodeType instanceof ConstantBooleanType) {
97101
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode, $tipText): RuleErrorBuilder {
@@ -107,7 +111,7 @@ public function processNode(
107111
return $ruleErrorBuilder->tip($tipText);
108112
};
109113

110-
if (!$scope->isInFirstLevelStatement()) {
114+
if ($nodeType->getValue() === false || $originalNode->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) !== true) {
111115
$messages[] = $addTip(RuleErrorBuilder::message(sprintf(
112116
'Result of %s is always %s.',
113117
$nodeText,

Diff for: src/Rules/Comparison/ConstantConditionRuleHelper.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ public function __construct(
2121

2222
public function shouldReportAlwaysTrueByDefault(Expr $expr): bool
2323
{
24-
if ($expr->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) === true) {
25-
return false;
26-
}
27-
2824
return $expr instanceof Expr\BooleanNot
2925
|| $expr instanceof Expr\BinaryOp\BooleanOr
3026
|| $expr instanceof Expr\BinaryOp\BooleanAnd
@@ -54,6 +50,11 @@ public function shouldSkip(Scope $scope, Expr $expr): bool
5450
return true;
5551
}
5652

53+
if ($expr->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME) === true) {
54+
// always-true should not be reported because last condition
55+
return true;
56+
}
57+
5758
if (
5859
$expr instanceof FuncCall
5960
|| $expr instanceof MethodCall

Diff for: tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ public function testRule(): void
118118
'Right side of && is always true.',
119119
147,
120120
],
121+
[
122+
'Left side of && is always true.',
123+
178,
124+
],
125+
[
126+
'Right side of && is always true.',
127+
178,
128+
],
121129
]);
122130
}
123131

Diff for: tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public function testRule(): void
6464
'Negated boolean expression is always false.',
6565
50,
6666
],
67+
[
68+
'Negated boolean expression is always true.',
69+
67,
70+
],
6771
]);
6872
}
6973

Diff for: tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ public function testRule(): void
110110
'Right side of || is always true.',
111111
85,
112112
],
113+
[
114+
'Left side of || is always true.',
115+
101,
116+
],
117+
[
118+
'Right side of || is always true.',
119+
110,
120+
],
113121
]);
114122
}
115123

Diff for: tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function testRule(): void
4141
$this->analyse([__DIR__ . '/data/elseif-condition.php'], [
4242
[
4343
'Elseif condition is always true.',
44-
18,
44+
56,
4545
],
4646
]);
4747
}
@@ -52,7 +52,7 @@ public function testDoNotReportPhpDoc(): void
5252
$this->analyse([__DIR__ . '/data/elseif-condition-not-phpdoc.php'], [
5353
[
5454
'Elseif condition is always true.',
55-
18,
55+
46,
5656
],
5757
]);
5858
}
@@ -63,11 +63,11 @@ public function testReportPhpDoc(): void
6363
$this->analyse([__DIR__ . '/data/elseif-condition-not-phpdoc.php'], [
6464
[
6565
'Elseif condition is always true.',
66-
18,
66+
46,
6767
],
6868
[
6969
'Elseif condition is always true.',
70-
24,
70+
56,
7171
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
7272
],
7373
]);

Diff for: tests/PHPStan/Rules/Comparison/data/boolean-and.php

+16
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,19 @@ function bug1924() {
164164
if (isset($arr['a']) && isset($arr['b'])) {
165165
}
166166
}
167+
168+
class ConditionalAlwaysTrue
169+
{
170+
public function sayHello(int $i): void
171+
{
172+
$one = 1;
173+
if ($i < 5) {
174+
} elseif ($one && $i) { // always-true should not be reported because last condition
175+
}
176+
177+
if ($i < 5) {
178+
} elseif ($one && $i) { // always-true should be reported, because another condition below
179+
} elseif (rand(0,1)) {
180+
}
181+
}
182+
}

Diff for: tests/PHPStan/Rules/Comparison/data/boolean-not.php

+19
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,22 @@ public function nestedIfConditions($mixed): void
5252
}
5353
}
5454
}
55+
56+
class ConditionalAlwaysTrue
57+
{
58+
public function doFoo(int $i)
59+
{
60+
$zero = 0;
61+
if ($i < 0) {
62+
} elseif (!$zero) { // always-true should not be reported because last condition
63+
}
64+
65+
$zero = 0;
66+
if ($i < 0) {
67+
} elseif (!$zero) { // always-true should be reported, because another condition below
68+
} elseif (rand(0,1)) {
69+
}
70+
}
71+
72+
}
73+

Diff for: tests/PHPStan/Rules/Comparison/data/boolean-or.php

+26
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,29 @@ public function orInIfCondition($mixed, int $i): void
8787
}
8888
}
8989
}
90+
91+
class ConditionalAlwaysTrue
92+
{
93+
public function doFoo(int $i, $x)
94+
{
95+
$one = 1;
96+
if ($x) {
97+
} elseif ($one || $i) { // always-true should not be reported because last condition
98+
}
99+
100+
if ($x) {
101+
} elseif ($one || $i) { // always-true should be reported, because another condition below
102+
} elseif (rand(0,1)) {
103+
}
104+
105+
if ($x) {
106+
} elseif ($i || $one) { // always-true should not be reported because last condition
107+
}
108+
109+
if ($x) {
110+
} elseif ($i || $one) { // always-true should be reported, because another condition below
111+
} elseif (rand(0,1)) {
112+
}
113+
}
114+
115+
}

Diff for: tests/PHPStan/Rules/Comparison/data/elseif-condition-not-phpdoc.php

+34
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,37 @@ public function doFoo(
2727
}
2828

2929
}
30+
31+
class ConditionalAlwaysTrue
32+
{
33+
/**
34+
* @param object $object
35+
*/
36+
public function doFoo(
37+
self $self,
38+
$object
39+
): void
40+
{
41+
if (rand(0, 1)) {
42+
} elseif ($self) { // always-true should not be reported because last condition
43+
}
44+
45+
if (rand(0, 1)) {
46+
} elseif ($self) { // always-true should be reported, because another condition below
47+
} elseif (rand(0,1)) {
48+
}
49+
50+
51+
if (rand(0, 1)) {
52+
} elseif ($object) { // always-true should not be reported because last condition
53+
}
54+
55+
if (rand(0, 1)) {
56+
} elseif ($object) { // always-true should be reported, because another condition below
57+
} elseif (rand(0,1)) {
58+
}
59+
60+
}
61+
62+
}
63+

Diff for: tests/PHPStan/Rules/Comparison/data/elseif-condition.php

+20
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,23 @@ public function doFoo(int $i, \stdClass $std, $union, $intersection)
3939
}
4040

4141
}
42+
43+
class ConditionalAlwaysTrue
44+
{
45+
/**
46+
* @param int $i
47+
* @param \stdClass $std
48+
*/
49+
public function doFoo(int $i, \stdClass $std)
50+
{
51+
if ($i) {
52+
} elseif ($std) { // always-true should not be reported because last condition
53+
}
54+
55+
if ($i) {
56+
} elseif ($std) { // always-true should be reported, because another condition below
57+
} elseif (rand(0,1)) {
58+
}
59+
}
60+
61+
}

0 commit comments

Comments
 (0)