Skip to content

Commit b7dd96a

Browse files
kamil-zacekondrejmirtes
authored andcommitted
Strict operator type check for assign operations
1 parent 66b378f commit b7dd96a

15 files changed

+317
-90
lines changed

phpstan-baseline.neon

-30
Original file line numberDiff line numberDiff line change
@@ -107,61 +107,31 @@ parameters:
107107
count: 1
108108
path: src/Rules/Operators/OperandsInArithmeticAdditionRule.php
109109

110-
-
111-
message: "#^Parameter \\#1 \\$node \\(PhpParser\\\\Node\\\\Expr\\\\BinaryOp\\\\BooleanAnd\\) of method PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticAdditionRule\\:\\:processNode\\(\\) should be contravariant with parameter \\$node \\(PhpParser\\\\Node\\) of method PHPStan\\\\Rules\\\\Rule\\<PhpParser\\\\Node\\>\\:\\:processNode\\(\\)$#"
112-
count: 1
113-
path: src/Rules/Operators/OperandsInArithmeticAdditionRule.php
114-
115110
-
116111
message: "#^Class PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticDivisionRule implements generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#"
117112
count: 1
118113
path: src/Rules/Operators/OperandsInArithmeticDivisionRule.php
119114

120-
-
121-
message: "#^Parameter \\#1 \\$node \\(PhpParser\\\\Node\\\\Expr\\\\BinaryOp\\\\BooleanAnd\\) of method PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticDivisionRule\\:\\:processNode\\(\\) should be contravariant with parameter \\$node \\(PhpParser\\\\Node\\) of method PHPStan\\\\Rules\\\\Rule\\<PhpParser\\\\Node\\>\\:\\:processNode\\(\\)$#"
122-
count: 1
123-
path: src/Rules/Operators/OperandsInArithmeticDivisionRule.php
124-
125115
-
126116
message: "#^Class PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticExponentiationRule implements generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#"
127117
count: 1
128118
path: src/Rules/Operators/OperandsInArithmeticExponentiationRule.php
129119

130-
-
131-
message: "#^Parameter \\#1 \\$node \\(PhpParser\\\\Node\\\\Expr\\\\BinaryOp\\\\BooleanAnd\\) of method PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticExponentiationRule\\:\\:processNode\\(\\) should be contravariant with parameter \\$node \\(PhpParser\\\\Node\\) of method PHPStan\\\\Rules\\\\Rule\\<PhpParser\\\\Node\\>\\:\\:processNode\\(\\)$#"
132-
count: 1
133-
path: src/Rules/Operators/OperandsInArithmeticExponentiationRule.php
134-
135120
-
136121
message: "#^Class PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticModuloRule implements generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#"
137122
count: 1
138123
path: src/Rules/Operators/OperandsInArithmeticModuloRule.php
139124

140-
-
141-
message: "#^Parameter \\#1 \\$node \\(PhpParser\\\\Node\\\\Expr\\\\BinaryOp\\\\BooleanAnd\\) of method PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticModuloRule\\:\\:processNode\\(\\) should be contravariant with parameter \\$node \\(PhpParser\\\\Node\\) of method PHPStan\\\\Rules\\\\Rule\\<PhpParser\\\\Node\\>\\:\\:processNode\\(\\)$#"
142-
count: 1
143-
path: src/Rules/Operators/OperandsInArithmeticModuloRule.php
144-
145125
-
146126
message: "#^Class PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticMultiplicationRule implements generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#"
147127
count: 1
148128
path: src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php
149129

150-
-
151-
message: "#^Parameter \\#1 \\$node \\(PhpParser\\\\Node\\\\Expr\\\\BinaryOp\\\\BooleanAnd\\) of method PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticMultiplicationRule\\:\\:processNode\\(\\) should be contravariant with parameter \\$node \\(PhpParser\\\\Node\\) of method PHPStan\\\\Rules\\\\Rule\\<PhpParser\\\\Node\\>\\:\\:processNode\\(\\)$#"
152-
count: 1
153-
path: src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php
154-
155130
-
156131
message: "#^Class PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticSubtractionRule implements generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#"
157132
count: 1
158133
path: src/Rules/Operators/OperandsInArithmeticSubtractionRule.php
159134

160-
-
161-
message: "#^Parameter \\#1 \\$node \\(PhpParser\\\\Node\\\\Expr\\\\BinaryOp\\\\BooleanAnd\\) of method PHPStan\\\\Rules\\\\Operators\\\\OperandsInArithmeticSubtractionRule\\:\\:processNode\\(\\) should be contravariant with parameter \\$node \\(PhpParser\\\\Node\\) of method PHPStan\\\\Rules\\\\Rule\\<PhpParser\\\\Node\\>\\:\\:processNode\\(\\)$#"
162-
count: 1
163-
path: src/Rules/Operators/OperandsInArithmeticSubtractionRule.php
164-
165135
-
166136
message: "#^Class PHPStan\\\\Rules\\\\StrictCalls\\\\DynamicCallOnStaticMethodsRule implements generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#"
167137
count: 1

rules.neon

+12
Original file line numberDiff line numberDiff line change
@@ -204,21 +204,33 @@ services:
204204

205205
-
206206
class: PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule
207+
arguments:
208+
bleedingEdge: %featureToggles.bleedingEdge%
207209

208210
-
209211
class: PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule
212+
arguments:
213+
bleedingEdge: %featureToggles.bleedingEdge%
210214

211215
-
212216
class: PHPStan\Rules\Operators\OperandsInArithmeticExponentiationRule
217+
arguments:
218+
bleedingEdge: %featureToggles.bleedingEdge%
213219

214220
-
215221
class: PHPStan\Rules\Operators\OperandsInArithmeticModuloRule
222+
arguments:
223+
bleedingEdge: %featureToggles.bleedingEdge%
216224

217225
-
218226
class: PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule
227+
arguments:
228+
bleedingEdge: %featureToggles.bleedingEdge%
219229

220230
-
221231
class: PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule
232+
arguments:
233+
bleedingEdge: %featureToggles.bleedingEdge%
222234

223235
-
224236
class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule

src/Rules/Operators/OperandsInArithmeticAdditionRule.php

+23-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PhpParser\Node;
6-
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
7-
use PhpParser\Node\Expr\BinaryOp\Plus;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\AssignOp\Plus as AssignOpPlus;
8+
use PhpParser\Node\Expr\BinaryOp\Plus as BinaryOpPlus;
89
use PHPStan\Analyser\Scope;
910
use PHPStan\Rules\Rule;
1011
use PHPStan\Type\VerbosityLevel;
@@ -17,36 +18,49 @@ class OperandsInArithmeticAdditionRule implements Rule
1718
/** @var OperatorRuleHelper */
1819
private $helper;
1920

20-
public function __construct(OperatorRuleHelper $helper)
21+
/** @var bool */
22+
private $bleedingEdge;
23+
24+
public function __construct(OperatorRuleHelper $helper, bool $bleedingEdge)
2125
{
2226
$this->helper = $helper;
27+
$this->bleedingEdge = $bleedingEdge;
2328
}
2429

2530
public function getNodeType(): string
2631
{
27-
return Plus::class;
32+
return Expr::class;
2833
}
2934

3035
/**
31-
* @param BooleanAnd $node
3236
* @return string[] errors
3337
*/
3438
public function processNode(Node $node, Scope $scope): array
3539
{
36-
$leftType = $scope->getType($node->left);
37-
$rightType = $scope->getType($node->right);
40+
if ($node instanceof BinaryOpPlus) {
41+
$left = $node->left;
42+
$right = $node->right;
43+
} elseif ($node instanceof AssignOpPlus && $this->bleedingEdge) {
44+
$left = $node->var;
45+
$right = $node->expr;
46+
} else {
47+
return [];
48+
}
49+
50+
$leftType = $scope->getType($left);
51+
$rightType = $scope->getType($right);
3852
if (count($leftType->getArrays()) > 0 && count($rightType->getArrays()) > 0) {
3953
return [];
4054
}
4155

4256
$messages = [];
43-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
57+
if (!$this->helper->isValidForArithmeticOperation($scope, $left)) {
4458
$messages[] = sprintf(
4559
'Only numeric types are allowed in +, %s given on the left side.',
4660
$leftType->describe(VerbosityLevel::typeOnly())
4761
);
4862
}
49-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
63+
if (!$this->helper->isValidForArithmeticOperation($scope, $right)) {
5064
$messages[] = sprintf(
5165
'Only numeric types are allowed in +, %s given on the right side.',
5266
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticDivisionRule.php

+23-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PhpParser\Node;
6-
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
7-
use PhpParser\Node\Expr\BinaryOp\Div;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\AssignOp\Div as AssignOpDiv;
8+
use PhpParser\Node\Expr\BinaryOp\Div as BinaryOpDiv;
89
use PHPStan\Analyser\Scope;
910
use PHPStan\Rules\Rule;
1011
use PHPStan\Type\VerbosityLevel;
@@ -16,33 +17,46 @@ class OperandsInArithmeticDivisionRule implements Rule
1617
/** @var OperatorRuleHelper */
1718
private $helper;
1819

19-
public function __construct(OperatorRuleHelper $helper)
20+
/** @var bool */
21+
private $bleedingEdge;
22+
23+
public function __construct(OperatorRuleHelper $helper, bool $bleedingEdge)
2024
{
2125
$this->helper = $helper;
26+
$this->bleedingEdge = $bleedingEdge;
2227
}
2328

2429
public function getNodeType(): string
2530
{
26-
return Div::class;
31+
return Expr::class;
2732
}
2833

2934
/**
30-
* @param BooleanAnd $node
3135
* @return string[] errors
3236
*/
3337
public function processNode(Node $node, Scope $scope): array
3438
{
39+
if ($node instanceof BinaryOpDiv) {
40+
$left = $node->left;
41+
$right = $node->right;
42+
} elseif ($node instanceof AssignOpDiv && $this->bleedingEdge) {
43+
$left = $node->var;
44+
$right = $node->expr;
45+
} else {
46+
return [];
47+
}
48+
3549
$messages = [];
36-
$leftType = $scope->getType($node->left);
37-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
50+
$leftType = $scope->getType($left);
51+
if (!$this->helper->isValidForArithmeticOperation($scope, $left)) {
3852
$messages[] = sprintf(
3953
'Only numeric types are allowed in /, %s given on the left side.',
4054
$leftType->describe(VerbosityLevel::typeOnly())
4155
);
4256
}
4357

44-
$rightType = $scope->getType($node->right);
45-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
58+
$rightType = $scope->getType($right);
59+
if (!$this->helper->isValidForArithmeticOperation($scope, $right)) {
4660
$messages[] = sprintf(
4761
'Only numeric types are allowed in /, %s given on the right side.',
4862
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticExponentiationRule.php

+23-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PhpParser\Node;
6-
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
7-
use PhpParser\Node\Expr\BinaryOp\Pow;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\AssignOp\Pow as AssignOpPow;
8+
use PhpParser\Node\Expr\BinaryOp\Pow as BinaryOpPow;
89
use PHPStan\Analyser\Scope;
910
use PHPStan\Rules\Rule;
1011
use PHPStan\Type\VerbosityLevel;
@@ -16,33 +17,46 @@ class OperandsInArithmeticExponentiationRule implements Rule
1617
/** @var OperatorRuleHelper */
1718
private $helper;
1819

19-
public function __construct(OperatorRuleHelper $helper)
20+
/** @var bool */
21+
private $bleedingEdge;
22+
23+
public function __construct(OperatorRuleHelper $helper, bool $bleedingEdge)
2024
{
2125
$this->helper = $helper;
26+
$this->bleedingEdge = $bleedingEdge;
2227
}
2328

2429
public function getNodeType(): string
2530
{
26-
return Pow::class;
31+
return Expr::class;
2732
}
2833

2934
/**
30-
* @param BooleanAnd $node
3135
* @return string[] errors
3236
*/
3337
public function processNode(Node $node, Scope $scope): array
3438
{
39+
if ($node instanceof BinaryOpPow) {
40+
$left = $node->left;
41+
$right = $node->right;
42+
} elseif ($node instanceof AssignOpPow && $this->bleedingEdge) {
43+
$left = $node->var;
44+
$right = $node->expr;
45+
} else {
46+
return [];
47+
}
48+
3549
$messages = [];
36-
$leftType = $scope->getType($node->left);
37-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
50+
$leftType = $scope->getType($left);
51+
if (!$this->helper->isValidForArithmeticOperation($scope, $left)) {
3852
$messages[] = sprintf(
3953
'Only numeric types are allowed in **, %s given on the left side.',
4054
$leftType->describe(VerbosityLevel::typeOnly())
4155
);
4256
}
4357

44-
$rightType = $scope->getType($node->right);
45-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
58+
$rightType = $scope->getType($right);
59+
if (!$this->helper->isValidForArithmeticOperation($scope, $right)) {
4660
$messages[] = sprintf(
4761
'Only numeric types are allowed in **, %s given on the right side.',
4862
$rightType->describe(VerbosityLevel::typeOnly())

src/Rules/Operators/OperandsInArithmeticModuloRule.php

+23-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PhpParser\Node;
6-
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
7-
use PhpParser\Node\Expr\BinaryOp\Mod;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\AssignOp\Mod as AssignOpMod;
8+
use PhpParser\Node\Expr\BinaryOp\Mod as BinaryOpMod;
89
use PHPStan\Analyser\Scope;
910
use PHPStan\Rules\Rule;
1011
use PHPStan\Type\VerbosityLevel;
@@ -16,33 +17,46 @@ class OperandsInArithmeticModuloRule implements Rule
1617
/** @var OperatorRuleHelper */
1718
private $helper;
1819

19-
public function __construct(OperatorRuleHelper $helper)
20+
/** @var bool */
21+
private $bleedingEdge;
22+
23+
public function __construct(OperatorRuleHelper $helper, bool $bleedingEdge)
2024
{
2125
$this->helper = $helper;
26+
$this->bleedingEdge = $bleedingEdge;
2227
}
2328

2429
public function getNodeType(): string
2530
{
26-
return Mod::class;
31+
return Expr::class;
2732
}
2833

2934
/**
30-
* @param BooleanAnd $node
3135
* @return string[] errors
3236
*/
3337
public function processNode(Node $node, Scope $scope): array
3438
{
39+
if ($node instanceof BinaryOpMod) {
40+
$left = $node->left;
41+
$right = $node->right;
42+
} elseif ($node instanceof AssignOpMod && $this->bleedingEdge) {
43+
$left = $node->var;
44+
$right = $node->expr;
45+
} else {
46+
return [];
47+
}
48+
3549
$messages = [];
36-
$leftType = $scope->getType($node->left);
37-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->left)) {
50+
$leftType = $scope->getType($left);
51+
if (!$this->helper->isValidForArithmeticOperation($scope, $left)) {
3852
$messages[] = sprintf(
3953
'Only numeric types are allowed in %%, %s given on the left side.',
4054
$leftType->describe(VerbosityLevel::typeOnly())
4155
);
4256
}
4357

44-
$rightType = $scope->getType($node->right);
45-
if (!$this->helper->isValidForArithmeticOperation($scope, $node->right)) {
58+
$rightType = $scope->getType($right);
59+
if (!$this->helper->isValidForArithmeticOperation($scope, $right)) {
4660
$messages[] = sprintf(
4761
'Only numeric types are allowed in %%, %s given on the right side.',
4862
$rightType->describe(VerbosityLevel::typeOnly())

0 commit comments

Comments
 (0)