Skip to content

Commit 40400ae

Browse files
committed
StrictComparisonOfDifferentTypesRule - tip about treatPhpDocTypesAsCertain
1 parent ea2670a commit 40400ae

File tree

3 files changed

+68
-4
lines changed

3 files changed

+68
-4
lines changed

src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,40 @@ public function processNode(Node $node, Scope $scope): array
4444
$leftType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node->left) : $scope->getNativeType($node->left);
4545
$rightType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node->right) : $scope->getNativeType($node->right);
4646

47+
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder {
48+
if (!$this->treatPhpDocTypesAsCertain) {
49+
return $ruleErrorBuilder;
50+
}
51+
52+
$instanceofTypeWithoutPhpDocs = $scope->getNativeType($node);
53+
if ($instanceofTypeWithoutPhpDocs instanceof ConstantBooleanType) {
54+
return $ruleErrorBuilder;
55+
}
56+
57+
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%</>.');
58+
};
59+
4760
if (!$nodeType->getValue()) {
4861
return [
49-
RuleErrorBuilder::message(sprintf(
62+
$addTip(RuleErrorBuilder::message(sprintf(
5063
'Strict comparison using %s between %s and %s will always evaluate to false.',
5164
$node instanceof Node\Expr\BinaryOp\Identical ? '===' : '!==',
5265
$leftType->describe(VerbosityLevel::value()),
5366
$rightType->describe(VerbosityLevel::value()),
54-
))->build(),
67+
)))->build(),
5568
];
5669
} elseif ($this->checkAlwaysTrueStrictComparison) {
5770
$isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME);
5871
if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) {
5972
return [];
6073
}
6174

62-
$errorBuilder = RuleErrorBuilder::message(sprintf(
75+
$errorBuilder = $addTip(RuleErrorBuilder::message(sprintf(
6376
'Strict comparison using %s between %s and %s will always evaluate to true.',
6477
$node instanceof Node\Expr\BinaryOp\Identical ? '===' : '!==',
6578
$leftType->describe(VerbosityLevel::value()),
6679
$rightType->describe(VerbosityLevel::value()),
67-
));
80+
)));
6881
if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) {
6982
$errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.');
7083
}

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool
3232
public function testStrictComparison(): void
3333
{
3434
$this->checkAlwaysTrueStrictComparison = true;
35+
$tipText = '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%</>.';
3536
$this->analyse(
3637
[__DIR__ . '/data/strict-comparison.php'],
3738
[
@@ -58,6 +59,7 @@ public function testStrictComparison(): void
5859
[
5960
'Strict comparison using === between 1 and array<StrictComparison\Foo>|bool|StrictComparison\Collection will always evaluate to false.',
6061
19,
62+
$tipText,
6163
],
6264
[
6365
'Strict comparison using === between true and false will always evaluate to false.',
@@ -110,10 +112,12 @@ public function testStrictComparison(): void
110112
[
111113
'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.',
112114
212,
115+
$tipText,
113116
],
114117
[
115118
'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.',
116119
255,
120+
$tipText,
117121
],
118122
[
119123
'Strict comparison using !== between stdClass and null will always evaluate to true.',
@@ -182,6 +186,7 @@ public function testStrictComparison(): void
182186
[
183187
'Strict comparison using === between int<0, 1> and 100 will always evaluate to false.',
184188
622,
189+
$tipText,
185190
],
186191
[
187192
'Strict comparison using === between 100 and \'foo\' will always evaluate to false.',
@@ -267,6 +272,7 @@ public function testStrictComparison(): void
267272
public function testStrictComparisonWithoutAlwaysTrue(): void
268273
{
269274
$this->checkAlwaysTrueStrictComparison = false;
275+
$tipText = '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%</>.';
270276
$this->analyse(
271277
[__DIR__ . '/data/strict-comparison.php'],
272278
[
@@ -285,6 +291,7 @@ public function testStrictComparisonWithoutAlwaysTrue(): void
285291
[
286292
'Strict comparison using === between 1 and array<StrictComparison\Foo>|bool|StrictComparison\Collection will always evaluate to false.',
287293
19,
294+
$tipText,
288295
],
289296
[
290297
'Strict comparison using === between true and false will always evaluate to false.',
@@ -377,6 +384,7 @@ public function testStrictComparisonWithoutAlwaysTrue(): void
377384
[
378385
'Strict comparison using === between int<0, 1> and 100 will always evaluate to false.',
379386
622,
387+
$tipText,
380388
],
381389
[
382390
'Strict comparison using === between 100 and \'foo\' will always evaluate to false.',
@@ -572,6 +580,7 @@ public function testBug7555(): void
572580
[
573581
'Strict comparison using === between 2 and 2 will always evaluate to true.',
574582
11,
583+
'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%</>.',
575584
],
576585
]);
577586
}
@@ -693,14 +702,17 @@ public function testBug4242(): void
693702
public function testBug3633(): void
694703
{
695704
$this->checkAlwaysTrueStrictComparison = true;
705+
$tipText = '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%</>.';
696706
$this->analyse([__DIR__ . '/data/bug-3633.php'], [
697707
[
698708
'Strict comparison using === between class-string<Bug3633\HelloWorld> and \'Bug3633\\\OtherClass\' will always evaluate to false.',
699709
37,
710+
$tipText,
700711
],
701712
[
702713
'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\HelloWorld\' will always evaluate to true.',
703714
41,
715+
$tipText,
704716
],
705717
[
706718
'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\OtherClass\' will always evaluate to false.',
@@ -709,38 +721,47 @@ public function testBug3633(): void
709721
[
710722
'Strict comparison using === between class-string<Bug3633\OtherClass> and \'Bug3633\\\HelloWorld\' will always evaluate to false.',
711723
64,
724+
$tipText,
712725
],
713726
[
714727
'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.',
715728
71,
729+
$tipText,
716730
],
717731
[
718732
'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\OtherClass\' will always evaluate to true.',
719733
74,
734+
$tipText,
720735
],
721736
[
722737
'Strict comparison using === between class-string<Bug3633\FinalClass> and \'Bug3633\\\HelloWorld\' will always evaluate to false.',
723738
93,
739+
$tipText,
724740
],
725741
[
726742
'Strict comparison using === between class-string<Bug3633\FinalClass> and \'Bug3633\\\OtherClass\' will always evaluate to false.',
727743
96,
744+
$tipText,
728745
],
729746
[
730747
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.',
731748
102,
749+
$tipText,
732750
],
733751
[
734752
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.',
735753
106,
754+
$tipText,
736755
],
737756
[
738757
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\OtherClass\' will always evaluate to false.',
739758
109,
759+
$tipText,
740760
],
741761
[
742762
'Strict comparison using !== between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to false.',
743763
112,
764+
$tipText,
744765
],
745766
[
746767
'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.',
@@ -903,4 +924,16 @@ public function testBug5978(): void
903924
$this->analyse([__DIR__ . '/data/bug-5978.php'], $expectedErrors);
904925
}
905926

927+
public function testBug9104(): void
928+
{
929+
$this->checkAlwaysTrueStrictComparison = true;
930+
$this->analyse([__DIR__ . '/data/bug-9104.php'], [
931+
[
932+
'Strict comparison using === between int<1, max> and 0 will always evaluate to false.',
933+
12,
934+
'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%</>.',
935+
],
936+
]);
937+
}
938+
906939
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Bug9104;
4+
5+
class ArrayLibrary
6+
{
7+
/**
8+
* @param non-empty-list<int> $list
9+
*/
10+
public function getFirst(array $list): int
11+
{
12+
if (count($list) === 0) {
13+
throw new \LogicException('empty array');
14+
}
15+
16+
return $list[0];
17+
}
18+
}

0 commit comments

Comments
 (0)