Skip to content

Commit a670a59

Browse files
committed
UselessCastRule - respect treatPhpDocTypesAsCertain
1 parent 08f2e51 commit a670a59

File tree

5 files changed

+96
-7
lines changed

5 files changed

+96
-7
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
],
88
"require": {
99
"php": "~7.1",
10-
"phpstan/phpstan": "^0.12"
10+
"phpstan/phpstan": "^0.12.6"
1111
},
1212
"require-dev": {
1313
"consistence/coding-standard": "^3.0.1",

rules.neon

+8-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ rules:
1717
- PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule
1818
- PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule
1919
- PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule
20-
- PHPStan\Rules\Cast\UselessCastRule
2120
- PHPStan\Rules\Classes\RequireParentConstructCallRule
2221
- PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule
2322
- PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule
@@ -45,6 +44,14 @@ rules:
4544
services:
4645
-
4746
class: PHPStan\Rules\BooleansInConditions\BooleanRuleHelper
47+
48+
-
49+
class: PHPStan\Rules\Cast\UselessCastRule
50+
arguments:
51+
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
52+
tags:
53+
- phpstan.rules.rule
54+
4855
-
4956
class: PHPStan\Rules\Operators\OperatorRuleHelper
5057
-

src/Rules/Cast/UselessCastRule.php

+30-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,23 @@
55
use PhpParser\Node;
66
use PhpParser\Node\Expr\Cast;
77
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\RuleError;
9+
use PHPStan\Rules\RuleErrorBuilder;
810
use PHPStan\Type\ErrorType;
911
use PHPStan\Type\TypeUtils;
1012
use PHPStan\Type\VerbosityLevel;
1113

1214
class UselessCastRule implements \PHPStan\Rules\Rule
1315
{
1416

17+
/** @var bool */
18+
private $treatPhpDocTypesAsCertain;
19+
20+
public function __construct(bool $treatPhpDocTypesAsCertain)
21+
{
22+
$this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain;
23+
}
24+
1525
public function getNodeType(): string
1626
{
1727
return Cast::class;
@@ -20,7 +30,7 @@ public function getNodeType(): string
2030
/**
2131
* @param \PhpParser\Node\Expr\Cast $node
2232
* @param \PHPStan\Analyser\Scope $scope
23-
* @return string[] errors
33+
* @return RuleError[] errors
2434
*/
2535
public function processNode(Node $node, Scope $scope): array
2636
{
@@ -30,14 +40,30 @@ public function processNode(Node $node, Scope $scope): array
3040
}
3141
$castType = TypeUtils::generalizeType($castType);
3242

33-
$expressionType = $scope->getType($node->expr);
43+
if ($this->treatPhpDocTypesAsCertain) {
44+
$expressionType = $scope->getType($node->expr);
45+
} else {
46+
$expressionType = $scope->getNativeType($node->expr);
47+
}
3448
if ($castType->isSuperTypeOf($expressionType)->yes()) {
49+
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $castType): RuleErrorBuilder {
50+
if (!$this->treatPhpDocTypesAsCertain) {
51+
return $ruleErrorBuilder;
52+
}
53+
54+
$expressionTypeWithoutPhpDoc = $scope->getNativeType($node->expr);
55+
if ($castType->isSuperTypeOf($expressionTypeWithoutPhpDoc)->yes()) {
56+
return $ruleErrorBuilder;
57+
}
58+
59+
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%</>.');
60+
};
3561
return [
36-
sprintf(
62+
$addTip(RuleErrorBuilder::message(sprintf(
3763
'Casting to %s something that\'s already %s.',
3864
$castType->describe(VerbosityLevel::typeOnly()),
3965
$expressionType->describe(VerbosityLevel::typeOnly())
40-
),
66+
)))->build(),
4167
];
4268
}
4369

tests/Rules/Cast/UselessCastRuleTest.php

+37-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@
55
class UselessCastRuleTest extends \PHPStan\Testing\RuleTestCase
66
{
77

8+
/** @var bool */
9+
private $treatPhpDocTypesAsCertain;
10+
811
protected function getRule(): \PHPStan\Rules\Rule
912
{
10-
return new UselessCastRule();
13+
return new UselessCastRule($this->treatPhpDocTypesAsCertain);
14+
}
15+
16+
protected function shouldTreatPhpDocTypesAsCertain(): bool
17+
{
18+
return $this->treatPhpDocTypesAsCertain;
1119
}
1220

1321
public function testUselessCast(): void
1422
{
1523
require_once __DIR__ . '/data/useless-cast.php';
24+
$this->treatPhpDocTypesAsCertain = true;
1625
$this->analyse(
1726
[__DIR__ . '/data/useless-cast.php'],
1827
[
@@ -40,4 +49,31 @@ public function testUselessCast(): void
4049
);
4150
}
4251

52+
public function testDoNotReportPhpDoc(): void
53+
{
54+
$this->treatPhpDocTypesAsCertain = false;
55+
$this->analyse([__DIR__ . '/data/useless-cast-not-phpdoc.php'], [
56+
[
57+
'Casting to int something that\'s already int.',
58+
16,
59+
],
60+
]);
61+
}
62+
63+
public function testReportPhpDoc(): void
64+
{
65+
$this->treatPhpDocTypesAsCertain = true;
66+
$this->analyse([__DIR__ . '/data/useless-cast-not-phpdoc.php'], [
67+
[
68+
'Casting to int something that\'s already int.',
69+
16,
70+
],
71+
[
72+
'Casting to int something that\'s already int.',
73+
17,
74+
'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%</>.',
75+
],
76+
]);
77+
}
78+
4379
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace UselessCastNotPhpDoc;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param int $phpDocInteger
10+
*/
11+
public function doFoo(
12+
int $realInteger,
13+
$phpDocInteger
14+
): void
15+
{
16+
$foo = (int) $realInteger;
17+
$bar = (int) $phpDocInteger;
18+
}
19+
20+
}

0 commit comments

Comments
 (0)