Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a1e697b

Browse files
schlndhondrejmirtes
authored andcommitted
Bleeding edge: stricter ++/-- operator check
1 parent 04f8636 commit a1e697b

File tree

6 files changed

+266
-15
lines changed

6 files changed

+266
-15
lines changed

‎conf/config.level0.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ services:
205205
tags:
206206
- phpstan.rules.rule
207207
arguments:
208+
bleedingEdge: %featureToggles.bleedingEdge%
208209
checkThisOnly: %checkThisOnly%
209210

210211
-

‎src/Rules/Operators/InvalidIncDecOperationRule.php‎

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Rules\RuleErrorBuilder;
9+
use PHPStan\Rules\RuleLevelHelper;
910
use PHPStan\ShouldNotHappenException;
11+
use PHPStan\Type\BooleanType;
1012
use PHPStan\Type\ErrorType;
13+
use PHPStan\Type\FloatType;
14+
use PHPStan\Type\IntegerType;
15+
use PHPStan\Type\NullType;
16+
use PHPStan\Type\ObjectType;
17+
use PHPStan\Type\StringType;
18+
use PHPStan\Type\Type;
19+
use PHPStan\Type\UnionType;
1120
use PHPStan\Type\VerbosityLevel;
1221
use function get_class;
1322
use function sprintf;
@@ -18,7 +27,11 @@
1827
class InvalidIncDecOperationRule implements Rule
1928
{
2029

21-
public function __construct(private bool $checkThisOnly)
30+
public function __construct(
31+
private RuleLevelHelper $ruleLevelHelper,
32+
private bool $bleedingEdge,
33+
private bool $checkThisOnly,
34+
)
2235
{
2336
}
2437

@@ -74,28 +87,42 @@ public function processNode(Node $node, Scope $scope): array
7487
];
7588
}
7689

77-
if (!$this->checkThisOnly) {
90+
if (!$this->bleedingEdge) {
91+
if ($this->checkThisOnly) {
92+
return [];
93+
}
94+
7895
$varType = $scope->getType($node->var);
7996
if (!$varType->toString() instanceof ErrorType) {
8097
return [];
8198
}
8299
if (!$varType->toNumber() instanceof ErrorType) {
83100
return [];
84101
}
102+
} else {
103+
$allowedTypes = new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType(), new ObjectType('SimpleXMLElement')]);
104+
$varType = $this->ruleLevelHelper->findTypeToCheck(
105+
$scope,
106+
$node->var,
107+
'',
108+
static fn (Type $type): bool => $allowedTypes->isSuperTypeOf($type)->yes(),
109+
)->getType();
85110

86-
return [
87-
RuleErrorBuilder::message(sprintf(
88-
'Cannot use %s on %s.',
89-
$operatorString,
90-
$varType->describe(VerbosityLevel::value()),
91-
))
92-
->line($node->var->getStartLine())
93-
->identifier(sprintf('%s.type', $nodeType))
94-
->build(),
95-
];
111+
if ($varType instanceof ErrorType || $allowedTypes->isSuperTypeOf($varType)->yes()) {
112+
return [];
113+
}
96114
}
97115

98-
return [];
116+
return [
117+
RuleErrorBuilder::message(sprintf(
118+
'Cannot use %s on %s.',
119+
$operatorString,
120+
$varType->describe(VerbosityLevel::value()),
121+
))
122+
->line($node->var->getStartLine())
123+
->identifier(sprintf('%s.type', $nodeType))
124+
->build(),
125+
];
99126
}
100127

101128
}

‎tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php‎

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67
use PHPStan\Testing\RuleTestCase;
78

89
/**
@@ -11,9 +12,17 @@
1112
class InvalidIncDecOperationRuleTest extends RuleTestCase
1213
{
1314

15+
private bool $checkExplicitMixed = false;
16+
17+
private bool $checkImplicitMixed = false;
18+
1419
protected function getRule(): Rule
1520
{
16-
return new InvalidIncDecOperationRule(false);
21+
return new InvalidIncDecOperationRule(
22+
new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false),
23+
true,
24+
false,
25+
);
1726
}
1827

1928
public function testRule(): void
@@ -31,6 +40,108 @@ public function testRule(): void
3140
'Cannot use ++ on stdClass.',
3241
17,
3342
],
43+
[
44+
'Cannot use ++ on InvalidIncDec\\ClassWithToString.',
45+
19,
46+
],
47+
[
48+
'Cannot use -- on InvalidIncDec\\ClassWithToString.',
49+
21,
50+
],
51+
[
52+
'Cannot use ++ on array{}.',
53+
23,
54+
],
55+
[
56+
'Cannot use -- on array{}.',
57+
25,
58+
],
59+
[
60+
'Cannot use ++ on resource.',
61+
28,
62+
],
63+
[
64+
'Cannot use -- on resource.',
65+
32,
66+
],
67+
]);
68+
}
69+
70+
public function testMixed(): void
71+
{
72+
$this->checkExplicitMixed = true;
73+
$this->checkImplicitMixed = true;
74+
$this->analyse([__DIR__ . '/data/invalid-inc-dec-mixed.php'], [
75+
[
76+
'Cannot use ++ on T of mixed.',
77+
12,
78+
],
79+
[
80+
'Cannot use ++ on T of mixed.',
81+
14,
82+
],
83+
[
84+
'Cannot use -- on T of mixed.',
85+
16,
86+
],
87+
[
88+
'Cannot use -- on T of mixed.',
89+
18,
90+
],
91+
[
92+
'Cannot use ++ on mixed.',
93+
24,
94+
],
95+
[
96+
'Cannot use ++ on mixed.',
97+
26,
98+
],
99+
[
100+
'Cannot use -- on mixed.',
101+
28,
102+
],
103+
[
104+
'Cannot use -- on mixed.',
105+
30,
106+
],
107+
[
108+
'Cannot use ++ on mixed.',
109+
36,
110+
],
111+
[
112+
'Cannot use ++ on mixed.',
113+
38,
114+
],
115+
[
116+
'Cannot use -- on mixed.',
117+
40,
118+
],
119+
[
120+
'Cannot use -- on mixed.',
121+
42,
122+
],
123+
]);
124+
}
125+
126+
public function testUnion(): void
127+
{
128+
$this->analyse([__DIR__ . '/data/invalid-inc-dec-union.php'], [
129+
[
130+
'Cannot use ++ on array|bool|float|int|object|string|null.',
131+
24,
132+
],
133+
[
134+
'Cannot use -- on array|bool|float|int|object|string|null.',
135+
26,
136+
],
137+
[
138+
'Cannot use ++ on (array|object).',
139+
29,
140+
],
141+
[
142+
'Cannot use -- on (array|object).',
143+
31,
144+
],
34145
]);
35146
}
36147

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace InvalidIncDecMixed;
4+
5+
/**
6+
* @template T
7+
* @param T $a
8+
*/
9+
function genericMixed(mixed $a): void
10+
{
11+
$b = $a;
12+
var_dump(++$b);
13+
$b = $a;
14+
var_dump($b++);
15+
$b = $a;
16+
var_dump(--$b);
17+
$b = $a;
18+
var_dump($b--);
19+
}
20+
21+
function explicitMixed(mixed $a): void
22+
{
23+
$b = $a;
24+
var_dump(++$b);
25+
$b = $a;
26+
var_dump($b++);
27+
$b = $a;
28+
var_dump(--$b);
29+
$b = $a;
30+
var_dump($b--);
31+
}
32+
33+
function implicitMixed($a): void
34+
{
35+
$b = $a;
36+
var_dump(++$b);
37+
$b = $a;
38+
var_dump($b++);
39+
$b = $a;
40+
var_dump(--$b);
41+
$b = $a;
42+
var_dump($b--);
43+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace InvalidIncDecUnion;
4+
5+
/**
6+
* @param __benevolent<scalar|null|array|object> $benevolentUnion
7+
* @param string|int|float|bool|null $okUnion
8+
* @param scalar|null|array|object $union
9+
* @param __benevolent<array|object> $badBenevolentUnion
10+
*/
11+
function foo($benevolentUnion, $okUnion, $union, $badBenevolentUnion): void
12+
{
13+
$a = $benevolentUnion;
14+
$a++;
15+
$a = $benevolentUnion;
16+
--$a;
17+
18+
$a = $okUnion;
19+
$a++;
20+
$a = $okUnion;
21+
--$a;
22+
23+
$a = $union;
24+
$a++;
25+
$a = $union;
26+
--$a;
27+
28+
$a = $badBenevolentUnion;
29+
$a++;
30+
$a = $badBenevolentUnion;
31+
--$a;
32+
}

‎tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace InvalidIncDec;
44

5-
function ($a, int $i, ?float $j, string $str, \stdClass $std) {
5+
function ($a, int $i, ?float $j, string $str, \stdClass $std, \SimpleXMLElement$simpleXMLElement) {
66
$a++;
77

88
$b = [1];
@@ -15,4 +15,41 @@ function ($a, int $i, ?float $j, string $str, \stdClass $std) {
1515
$j++;
1616
$str++;
1717
$std++;
18+
$classWithToString = new ClassWithToString();
19+
$classWithToString++;
20+
$classWithToString = new ClassWithToString();
21+
--$classWithToString;
22+
$arr = [];
23+
$arr++;
24+
$arr = [];
25+
--$arr;
26+
27+
if (($f = fopen('php://stdin', 'r')) !== false) {
28+
$f++;
29+
}
30+
31+
if (($f = fopen('php://stdin', 'r')) !== false) {
32+
--$f;
33+
}
34+
35+
$bool = true;
36+
$bool++;
37+
$bool = false;
38+
--$bool;
39+
$null = null;
40+
$null++;
41+
$null = null;
42+
--$null;
43+
$a = $simpleXMLElement;
44+
$a++;
45+
$a = $simpleXMLElement;
46+
--$a;
1847
};
48+
49+
class ClassWithToString
50+
{
51+
public function __toString(): string
52+
{
53+
return 'foo';
54+
}
55+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /