Skip to content

Commit 0ff7e44

Browse files
committed
InvalidIncDecOperationRule - make aware of deprecation of -- (PHP 8.3) and ++ (PHP 8.5) on non-numeric strings
1 parent 6ccdba2 commit 0ff7e44

File tree

7 files changed

+156
-11
lines changed

7 files changed

+156
-11
lines changed

src/Php/PhpVersion.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,4 +469,14 @@ public function supportsOverrideAttributeOnProperty(): bool
469469
return $this->versionId >= 80500;
470470
}
471471

472+
public function deprecatesDecOnNonNumericString(): bool
473+
{
474+
return $this->versionId >= 80300;
475+
}
476+
477+
public function deprecatesIncOnNonNumericString(): bool
478+
{
479+
return $this->versionId >= 80500;
480+
}
481+
472482
}

src/Rules/Operators/InvalidIncDecOperationRule.php

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\RegisteredRule;
8+
use PHPStan\Php\PhpVersion;
89
use PHPStan\Rules\Rule;
910
use PHPStan\Rules\RuleErrorBuilder;
1011
use PHPStan\Rules\RuleLevelHelper;
1112
use PHPStan\ShouldNotHappenException;
13+
use PHPStan\Type\Accessory\AccessoryNumericStringType;
1214
use PHPStan\Type\BooleanType;
1315
use PHPStan\Type\ErrorType;
1416
use PHPStan\Type\FloatType;
1517
use PHPStan\Type\IntegerType;
18+
use PHPStan\Type\IntersectionType;
1619
use PHPStan\Type\NullType;
1720
use PHPStan\Type\ObjectType;
1821
use PHPStan\Type\StringType;
@@ -31,6 +34,7 @@ final class InvalidIncDecOperationRule implements Rule
3134

3235
public function __construct(
3336
private RuleLevelHelper $ruleLevelHelper,
37+
private PhpVersion $phpVersion,
3438
)
3539
{
3640
}
@@ -87,7 +91,28 @@ public function processNode(Node $node, Scope $scope): array
8791
];
8892
}
8993

90-
$allowedTypes = new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType(), new ObjectType('SimpleXMLElement')]);
94+
$string = new StringType();
95+
$deprecatedString = false;
96+
if (
97+
(
98+
$this->phpVersion->deprecatesDecOnNonNumericString()
99+
&& (
100+
$node instanceof Node\Expr\PreDec
101+
|| $node instanceof Node\Expr\PostDec
102+
)
103+
) || (
104+
$this->phpVersion->deprecatesIncOnNonNumericString()
105+
&& (
106+
$node instanceof Node\Expr\PreInc
107+
|| $node instanceof Node\Expr\PostInc
108+
)
109+
)
110+
) {
111+
$string = new IntersectionType([$string, new AccessoryNumericStringType()]);
112+
$deprecatedString = true;
113+
}
114+
115+
$allowedTypes = new UnionType([new BooleanType(), new FloatType(), new IntegerType(), $string, new NullType(), new ObjectType('SimpleXMLElement')]);
91116
$varType = $this->ruleLevelHelper->findTypeToCheck(
92117
$scope,
93118
$node->var,
@@ -99,15 +124,24 @@ public function processNode(Node $node, Scope $scope): array
99124
return [];
100125
}
101126

102-
return [
103-
RuleErrorBuilder::message(sprintf(
104-
'Cannot use %s on %s.',
127+
$errorBuilder = RuleErrorBuilder::message(sprintf(
128+
'Cannot use %s on %s.',
129+
$operatorString,
130+
$varType->describe(VerbosityLevel::value()),
131+
))
132+
->line($node->var->getStartLine())
133+
->identifier(sprintf('%s.type', $nodeType));
134+
135+
if (!$varType->isString()->no() && $deprecatedString) {
136+
$errorBuilder->tip(sprintf(
137+
'Operator %s is deprecated for non-numeric-strings. Either narrow the type to numeric-string, or use %s().',
105138
$operatorString,
106-
$varType->describe(VerbosityLevel::value()),
107-
))
108-
->line($node->var->getStartLine())
109-
->identifier(sprintf('%s.type', $nodeType))
110-
->build(),
139+
$node instanceof Node\Expr\PreDec || $node instanceof Node\Expr\PostDec ? 'str_decrement' : 'str_increment',
140+
));
141+
}
142+
143+
return [
144+
$errorBuilder->build(),
111145
];
112146
}
113147

tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace PHPStan\Rules\Operators;
44

5+
use PHPStan\Php\PhpVersion;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Rules\RuleLevelHelper;
78
use PHPStan\Testing\RuleTestCase;
89
use PHPUnit\Framework\Attributes\RequiresPhp;
10+
use const PHP_VERSION_ID;
911

1012
/**
1113
* @extends RuleTestCase<InvalidIncDecOperationRule>
@@ -21,6 +23,7 @@ protected function getRule(): Rule
2123
{
2224
return new InvalidIncDecOperationRule(
2325
new RuleLevelHelper(self::createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true),
26+
new PhpVersion(PHP_VERSION_ID),
2427
);
2528
}
2629

@@ -129,10 +132,12 @@ public function testUnion(): void
129132
[
130133
'Cannot use ++ on array|bool|float|int|object|string|null.',
131134
24,
135+
'Operator ++ is deprecated for non-numeric-strings. Either narrow the type to numeric-string, or use str_increment().',
132136
],
133137
[
134138
'Cannot use -- on array|bool|float|int|object|string|null.',
135139
26,
140+
'Operator -- is deprecated for non-numeric-strings. Either narrow the type to numeric-string, or use str_decrement().',
136141
],
137142
[
138143
'Cannot use ++ on (array|object).',
@@ -145,4 +150,46 @@ public function testUnion(): void
145150
]);
146151
}
147152

153+
public function testDecNonNumericString(): void
154+
{
155+
$errors = [];
156+
if (PHP_VERSION_ID >= 80300) {
157+
$errors = [
158+
[
159+
'Cannot use -- on \'a\'.',
160+
21,
161+
'Operator -- is deprecated for non-numeric-strings. Either narrow the type to numeric-string, or use str_decrement().',
162+
],
163+
[
164+
'Cannot use -- on string.',
165+
23,
166+
'Operator -- is deprecated for non-numeric-strings. Either narrow the type to numeric-string, or use str_decrement().',
167+
],
168+
];
169+
}
170+
171+
$this->analyse([__DIR__ . '/data/dec-non-numeric-string.php'], $errors);
172+
}
173+
174+
public function testIncNonNumericString(): void
175+
{
176+
$errors = [];
177+
if (PHP_VERSION_ID >= 80500) {
178+
$errors = [
179+
[
180+
'Cannot use ++ on \'a\'.',
181+
21,
182+
'Operator ++ is deprecated for non-numeric-strings. Either narrow the type to numeric-string, or use str_increment().',
183+
],
184+
[
185+
'Cannot use ++ on string.',
186+
23,
187+
'Operator ++ is deprecated for non-numeric-strings. Either narrow the type to numeric-string, or use str_increment().',
188+
],
189+
];
190+
}
191+
192+
$this->analyse([__DIR__ . '/data/inc-non-numeric-string.php'], $errors);
193+
}
194+
148195
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace DecDecNonNumericString;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param string $s
10+
* @param numeric-string $t
11+
*/
12+
public function doFoo(
13+
string $s,
14+
string $t,
15+
): void
16+
{
17+
$a = '1';
18+
$a--;
19+
20+
$b = 'a';
21+
$b--;
22+
23+
$s--;
24+
$t--;
25+
}
26+
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace IncDecNonNumericString;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param string $s
10+
* @param numeric-string $t
11+
*/
12+
public function doFoo(
13+
string $s,
14+
string $t,
15+
): void
16+
{
17+
$a = '1';
18+
$a++;
19+
20+
$b = 'a';
21+
$b++;
22+
23+
$s++;
24+
$t++;
25+
}
26+
27+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
/**
66
* @param __benevolent<scalar|null|array|object> $benevolentUnion
7-
* @param string|int|float|bool|null $okUnion
7+
* @param numeric-string|int|float|bool|null $okUnion
88
* @param scalar|null|array|object $union
99
* @param __benevolent<array|object> $badBenevolentUnion
1010
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
function ($a, int $i, ?float $j, string $str, \stdClass $std, \SimpleXMLElement $simpleXMLElement) {
66
$a++;
7-
7+
if (!is_numeric($str)) return;
88
$b = [1];
99
$b[0]++;
1010

0 commit comments

Comments
 (0)