Skip to content

Commit

Permalink
Bleeding edge: stricter ++/-- operator check
Browse files Browse the repository at this point in the history
  • Loading branch information
schlndh authored and ondrejmirtes committed Jul 20, 2024
1 parent 04f8636 commit a1e697b
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 15 deletions.
1 change: 1 addition & 0 deletions conf/config.level0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ services:
tags:
- phpstan.rules.rule
arguments:
bleedingEdge: %featureToggles.bleedingEdge%
checkThisOnly: %checkThisOnly%

-
Expand Down
53 changes: 40 additions & 13 deletions src/Rules/Operators/InvalidIncDecOperationRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\BooleanType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use function get_class;
use function sprintf;
Expand All @@ -18,7 +27,11 @@
class InvalidIncDecOperationRule implements Rule
{

public function __construct(private bool $checkThisOnly)
public function __construct(
private RuleLevelHelper $ruleLevelHelper,
private bool $bleedingEdge,
private bool $checkThisOnly,
)
{
}

Expand Down Expand Up @@ -74,28 +87,42 @@ public function processNode(Node $node, Scope $scope): array
];
}

if (!$this->checkThisOnly) {
if (!$this->bleedingEdge) {
if ($this->checkThisOnly) {
return [];
}

$varType = $scope->getType($node->var);
if (!$varType->toString() instanceof ErrorType) {
return [];
}
if (!$varType->toNumber() instanceof ErrorType) {
return [];
}
} else {
$allowedTypes = new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType(), new ObjectType('SimpleXMLElement')]);
$varType = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$node->var,
'',
static fn (Type $type): bool => $allowedTypes->isSuperTypeOf($type)->yes(),
)->getType();

return [
RuleErrorBuilder::message(sprintf(
'Cannot use %s on %s.',
$operatorString,
$varType->describe(VerbosityLevel::value()),
))
->line($node->var->getStartLine())
->identifier(sprintf('%s.type', $nodeType))
->build(),
];
if ($varType instanceof ErrorType || $allowedTypes->isSuperTypeOf($varType)->yes()) {
return [];
}
}

return [];
return [
RuleErrorBuilder::message(sprintf(
'Cannot use %s on %s.',
$operatorString,
$varType->describe(VerbosityLevel::value()),
))
->line($node->var->getStartLine())
->identifier(sprintf('%s.type', $nodeType))
->build(),
];
}

}
113 changes: 112 additions & 1 deletion tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Operators;

use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;

/**
Expand All @@ -11,9 +12,17 @@
class InvalidIncDecOperationRuleTest extends RuleTestCase
{

private bool $checkExplicitMixed = false;

private bool $checkImplicitMixed = false;

protected function getRule(): Rule
{
return new InvalidIncDecOperationRule(false);
return new InvalidIncDecOperationRule(
new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false),
true,
false,
);
}

public function testRule(): void
Expand All @@ -31,6 +40,108 @@ public function testRule(): void
'Cannot use ++ on stdClass.',
17,
],
[
'Cannot use ++ on InvalidIncDec\\ClassWithToString.',
19,
],
[
'Cannot use -- on InvalidIncDec\\ClassWithToString.',
21,
],
[
'Cannot use ++ on array{}.',
23,
],
[
'Cannot use -- on array{}.',
25,
],
[
'Cannot use ++ on resource.',
28,
],
[
'Cannot use -- on resource.',
32,
],
]);
}

public function testMixed(): void
{
$this->checkExplicitMixed = true;
$this->checkImplicitMixed = true;
$this->analyse([__DIR__ . '/data/invalid-inc-dec-mixed.php'], [
[
'Cannot use ++ on T of mixed.',
12,
],
[
'Cannot use ++ on T of mixed.',
14,
],
[
'Cannot use -- on T of mixed.',
16,
],
[
'Cannot use -- on T of mixed.',
18,
],
[
'Cannot use ++ on mixed.',
24,
],
[
'Cannot use ++ on mixed.',
26,
],
[
'Cannot use -- on mixed.',
28,
],
[
'Cannot use -- on mixed.',
30,
],
[
'Cannot use ++ on mixed.',
36,
],
[
'Cannot use ++ on mixed.',
38,
],
[
'Cannot use -- on mixed.',
40,
],
[
'Cannot use -- on mixed.',
42,
],
]);
}

public function testUnion(): void
{
$this->analyse([__DIR__ . '/data/invalid-inc-dec-union.php'], [
[
'Cannot use ++ on array|bool|float|int|object|string|null.',
24,
],
[
'Cannot use -- on array|bool|float|int|object|string|null.',
26,
],
[
'Cannot use ++ on (array|object).',
29,
],
[
'Cannot use -- on (array|object).',
31,
],
]);
}

Expand Down
43 changes: 43 additions & 0 deletions tests/PHPStan/Rules/Operators/data/invalid-inc-dec-mixed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace InvalidIncDecMixed;

/**
* @template T
* @param T $a
*/
function genericMixed(mixed $a): void
{
$b = $a;
var_dump(++$b);
$b = $a;
var_dump($b++);
$b = $a;
var_dump(--$b);
$b = $a;
var_dump($b--);
}

function explicitMixed(mixed $a): void
{
$b = $a;
var_dump(++$b);
$b = $a;
var_dump($b++);
$b = $a;
var_dump(--$b);
$b = $a;
var_dump($b--);
}

function implicitMixed($a): void
{
$b = $a;
var_dump(++$b);
$b = $a;
var_dump($b++);
$b = $a;
var_dump(--$b);
$b = $a;
var_dump($b--);
}
32 changes: 32 additions & 0 deletions tests/PHPStan/Rules/Operators/data/invalid-inc-dec-union.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace InvalidIncDecUnion;

/**
* @param __benevolent<scalar|null|array|object> $benevolentUnion
* @param string|int|float|bool|null $okUnion
* @param scalar|null|array|object $union
* @param __benevolent<array|object> $badBenevolentUnion
*/
function foo($benevolentUnion, $okUnion, $union, $badBenevolentUnion): void
{
$a = $benevolentUnion;
$a++;
$a = $benevolentUnion;
--$a;

$a = $okUnion;
$a++;
$a = $okUnion;
--$a;

$a = $union;
$a++;
$a = $union;
--$a;

$a = $badBenevolentUnion;
$a++;
$a = $badBenevolentUnion;
--$a;
}
39 changes: 38 additions & 1 deletion tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace InvalidIncDec;

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

$b = [1];
Expand All @@ -15,4 +15,41 @@ function ($a, int $i, ?float $j, string $str, \stdClass $std) {
$j++;
$str++;
$std++;
$classWithToString = new ClassWithToString();
$classWithToString++;
$classWithToString = new ClassWithToString();
--$classWithToString;
$arr = [];
$arr++;
$arr = [];
--$arr;

if (($f = fopen('php://stdin', 'r')) !== false) {
$f++;
}

if (($f = fopen('php://stdin', 'r')) !== false) {
--$f;
}

$bool = true;
$bool++;
$bool = false;
--$bool;
$null = null;
$null++;
$null = null;
--$null;
$a = $simpleXMLElement;
$a++;
$a = $simpleXMLElement;
--$a;
};

class ClassWithToString
{
public function __toString(): string
{
return 'foo';
}
}

0 comments on commit a1e697b

Please sign in to comment.